From 8f4238a3d5d1c066cf679c2b8485a930be2476ce Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 21 Feb 2016 16:21:14 -0500 Subject: [PATCH 1/7] do type-inference using a work queue note: directly inferring linfo in pure_eval_call is unnecessary since abstract_call_gf_by_type would add this edge anyways and it can be harmful since there was no logic here to prevent unbounded recursion in the type signature fixes #9770 --- base/boot.jl | 3 + base/coreimg.jl | 15 +- base/inference.jl | 1198 ++++++++++++++++++++++++------------------ base/int.jl | 2 - base/reflection.jl | 6 +- base/sysimg.jl | 29 +- src/cgutils.cpp | 6 +- src/gf.c | 97 ++-- src/jltypes.c | 16 +- src/julia.h | 3 +- src/julia_internal.h | 2 +- src/toplevel.c | 6 +- test/choosetests.jl | 2 +- test/inference.jl | 24 + test/staged.jl | 2 +- 15 files changed, 783 insertions(+), 628 deletions(-) create mode 100644 test/inference.jl diff --git a/base/boot.jl b/base/boot.jl index 0e8cc513b89d0..d60941bc18ad6 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -340,6 +340,9 @@ unsafe_convert{T}(::Type{T}, x::T) = x (::Type{Array{T}}){T}(m::Int, n::Int) = Array{T,2}(m, n) (::Type{Array{T}}){T}(m::Int, n::Int, o::Int) = Array{T,3}(m, n, o) +(::Type{Array{T,1}}){T}() = Array{T,1}(0) +(::Type{Array{T,2}}){T}() = Array{T,2}(0, 0) + # TODO: possibly turn these into deprecations Array{T,N}(::Type{T}, d::NTuple{N,Int}) = Array{T}(d) Array{T}(::Type{T}, d::Int...) = Array{T}(d) diff --git a/base/coreimg.jl b/base/coreimg.jl index 4590a5ac4488b..fcb6eec947442 100644 --- a/base/coreimg.jl +++ b/base/coreimg.jl @@ -43,6 +43,7 @@ include("operators.jl") include("pointer.jl") const checked_add = + const checked_sub = - +(::Type{T}){T}(arg) = convert(T, arg)::T # core array operations include("abstractarray.jl") @@ -71,19 +72,5 @@ include("iterator.jl") # compiler include("inference.jl") -precompile(CallStack, (Expr, Module, Tuple{Void}, EmptyCallStack)) -precompile(_ieval, (Symbol,)) -precompile(abstract_eval, (LambdaInfo, ObjectIdDict, VarInfo)) -precompile(abstract_interpret, (Bool, ObjectIdDict, VarInfo)) -precompile(delete_var!, (Expr, Symbol)) -precompile(eval_annotate, (LambdaInfo, ObjectIdDict, VarInfo, ObjectIdDict, Array{Any,1})) -precompile(is_var_assigned, (Expr, Symbol)) -precompile(isconstantfunc, (SymbolNode, VarInfo)) -precompile(occurs_more, (Bool, Function, Int)) -precompile(occurs_more, (UInt8, Function, Int)) -precompile(occurs_undef, (Symbol, Expr)) -precompile(sym_replace, (UInt8, Array{Any,1}, Array{Any,1}, Array{Any,1}, Array{Any,1})) -precompile(symequal, (Symbol, Symbol)) - end # baremodule Inference )) diff --git a/base/inference.jl b/base/inference.jl index 75efb7b0872d9..8fd09d66952a9 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1,50 +1,54 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license -# parameters limiting potentially-infinite types +#### parameters limiting potentially-infinite types #### const MAX_TYPEUNION_LEN = 3 const MAX_TYPE_DEPTH = 7 -const MAX_TUPLETYPE_LEN = 42 +const MAX_TUPLETYPE_LEN = 8 const MAX_TUPLE_DEPTH = 4 -# avoid cycle due to over-specializing `any` when used by inference -function _any(f::ANY, a) - for x in a - f(x) && return true - end - return false -end - -immutable NotFound -end +#### inference state types #### +immutable NotFound end const NF = NotFound() +typealias LineNum Int +typealias VarTable ObjectIdDict + + +#TODO: the distinction between VarInfo and InferenceState is arbitrary and unnecessary type VarInfo + atypes #::Type # type sig + ast #::Expr + body::Array{Any,1} # ast body sp::SimpleVector # static parameters vars::Array{Any,1} # names of args and locals gensym_types::Array{Any,1} # types of the GenSym's in this function vinfo::Array{Any,1} # variable properties label_counter::Int # index of the current highest label for this function - fedbackvars::ObjectIdDict + fedbackvars::Dict{GenSym, Bool} mod::Module - linfo::LambdaInfo -end + currpc::LineNum + static_typeof::Bool + inf #::InferenceState -function VarInfo(linfo::LambdaInfo, ast=linfo.ast) - if !isa(ast,Expr) - ast = ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, ast) - end - vinflist = ast.args[2][1]::Array{Any,1} - vars = map(vi->vi[1], vinflist) - body = (ast.args[3].args)::Array{Any,1} - ngs = ast.args[2][3] - if !isa(ngs,Int) - ngs = length(ngs::Array) + function VarInfo(linfo::LambdaInfo, sig::ANY=linfo.specTypes, ast=linfo.ast) + if !isa(ast,Expr) + ast = ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, ast) + end + assert(is(ast.head,:lambda)) + vinflist = ast.args[2][1]::Array{Any,1} + vars = map(vi->vi[1], vinflist) + body = (ast.args[3].args)::Array{Any,1} + ngs = ast.args[2][3] + if !isa(ngs,Int) + ngs = length(ngs::Array) + end + gensym_types = Any[ NF for i = 1:(ngs::Int) ] + nl = label_counter(body)+1 + sp = linfo.sparam_vals + + return new(sig, ast, body, sp, vars, gensym_types, vinflist, nl, Dict{GenSym, Bool}(), linfo.module, 0, false) end - gensym_types = Any[ NF for i = 1:(ngs::Int) ] - nl = label_counter(body)+1 - sp = linfo.sparam_vals - VarInfo(sp, vars, gensym_types, vinflist, nl, ObjectIdDict(), linfo.module, linfo) end type VarState @@ -52,25 +56,146 @@ type VarState undef::Bool end -type EmptyCallStack -end - -type CallStack - ast - types::Type - recurred::Bool - cycleid::Int - result - prev::Union{EmptyCallStack,CallStack} +type InferenceState + # info on the state of inference and the linfo sv::VarInfo + linfo::LambdaInfo + args::Vector{Any} + labels::Vector{Int} + stmt_types::Vector{Any} + # return type + bestguess #::Type + # current active instruction pointers + ip::IntSet + nstmts::Int + # current exception handler info + cur_hand #::Tuple{LineNum, Tuple{LineNum, ...}} + handler_at::Vector{Any} + n_handlers::Int + # gensym sparsity and restart info + gensym_uses::Vector{IntSet} + gensym_init::Vector{Any} + # call-graph edges connecting from a caller to a callee (and back) + # we shouldn't need to iterate edges very often, so we use it to optimize the lookup from edge -> linenum + # whereas backedges is optimized for iteration + edges::ObjectIdDict #Dict{InferenceState, Vector{LineNum}} + backedges::Vector{Tuple{InferenceState, Vector{LineNum}}} + # iteration fixed-point detection + fixedpoint::Bool + typegotoredo::Bool + inworkq::Bool + optimize::Bool + inferred::Bool + tfunc_idx::Int + function InferenceState(sv::VarInfo, linfo::LambdaInfo, optimize::Bool) + body = sv.body + labels = zeros(Int, sv.label_counter) + for i = 1:length(body) + b = body[i] + if isa(b,LabelNode) + labels[b.label + 1] = i + end + end + + n = length(body) + s = Any[ () for i=1:n ] + # initial types + s[1] = ObjectIdDict() + for v in sv.vars + s[1][v] = VarState(Bottom,true) + end + + args = f_argnames(sv.ast) + la = length(args) + atypes = sv.atypes + if la > 0 + lastarg = sv.ast.args[1][la] + if is_rest_arg(lastarg) + if atypes === Tuple + if la > 1 + atypes = Tuple{Any[Any for i=1:la-1]..., Tuple.parameters[1]} + end + s[1][args[la]] = VarState(Tuple,false) + else + s[1][args[la]] = VarState(limit_tuple_depth(tupletype_tail(atypes,la)),false) + end + la -= 1 + end + end - CallStack(ast, types::ANY, prev) = new(ast, types, false, 0, Bottom, prev) + laty = length(atypes.parameters) + if laty > 0 + lastatype = atypes.parameters[laty] + if isvarargtype(lastatype) + lastatype = lastatype.parameters[1] + laty -= 1 + end + if isa(lastatype, TypeVar) + lastatype = lastatype.ub + end + if laty > la + laty = la + end + for i=1:laty + atyp = atypes.parameters[i] + if isa(atyp, TypeVar) + atyp = atyp.ub + end + s[1][args[i]] = VarState(atyp, false) + end + for i=laty+1:la + s[1][args[i]] = VarState(lastatype, false) + end + else + @assert la == 0 # wrong number of arguments + end + + gensym_uses = find_gensym_uses(body) + if length(sv.gensym_types) != length(gensym_uses) + sv.gensym_types = Any[ NF for i=1:length(gensym_uses) ] + end + gensym_init = copy(sv.gensym_types) + + # exception handlers + cur_hand = () + handler_at = Any[ 0 for i=1:n ] + n_handlers = 0 + + W = IntSet() + push!(W, 1) #initial pc to visit + + frame = new(sv, linfo, args, labels, s, Union{}, W, n, + cur_hand, handler_at, n_handlers, + gensym_uses, gensym_init, + ObjectIdDict(), #Dict{InferenceState, Vector{LineNum}}(), + Vector{Tuple{InferenceState, Vector{LineNum}}}(), + false, false, false, optimize, false, -1) + push!(active, frame) + nactive[] += 1 + sv.inf = frame + return frame + end end -inference_stack = EmptyCallStack() +#### current global inference state #### + +const active = Vector{Any}() # set of all InferenceState objects being processed +const nactive = Array{Int}(()) +nactive[] = 0 +const workq = Vector{InferenceState}() # set of InferenceState objects that can make immediate progress + +#### helper functions #### + +# avoid cycle due to over-specializing `any` when used by inference +function _any(f::ANY, a) + for x in a + f(x) && return true + end + return false +end function is_static_parameter(sv::VarInfo, s::Symbol) - sp = sv.linfo.sparam_syms + sp = sv.inf.linfo.sparam_syms for i=1:length(sp) if is(sp[i],s) return true @@ -115,6 +240,12 @@ end isknownlength(t::DataType) = !isvatuple(t) && !(t.name===NTuple.name && !isa(t.parameters[1],Int)) +# t[n:end] +tupletype_tail(t::ANY, n) = Tuple{t.parameters[n:end]...} + + +#### type-functions for builtins / intrinsics #### + cmp_tfunc = (x,y)->Bool isType(t::ANY) = isa(t,DataType) && is((t::DataType).name,Type.name) @@ -369,16 +500,15 @@ 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) +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(inference_stack,CallStack) && isa(Ai,Expr) && - is_known_call(Ai,tuple,inference_stack.sv) + elseif isa(Ai,Expr) && is_known_call(Ai,tuple,sv) tup = () for arg in Ai.args[2:end] - val = extract_simple_tparam(arg) + val = extract_simple_tparam(arg, sv) if val === Bottom return val end @@ -429,18 +559,18 @@ const apply_type_tfunc = function (A::ANY, args...) push!(tparams, aip1) else if i<=lA - val = extract_simple_tparam(A[i]) + val = extract_simple_tparam(A[i], global_sv::VarInfo) if val !== Bottom push!(tparams, val) continue - elseif isa(inference_stack,CallStack) && isa(A[i],Symbol) - sp = inference_stack.sv.linfo.sparam_syms + elseif isa(A[i],Symbol) + sp = global_sv.inf.linfo.sparam_syms s = A[i] found = false for j=1:length(sp) if is(sp[j],s) # static parameter - val = inference_stack.sv.sp[j] + val = global_sv.sp[j] if valid_tparam(val) push!(tparams, val) found = true @@ -657,9 +787,12 @@ let stagedcache=Dict{Any,Any}() end end -function abstract_call_gf(f::ANY, fargs, argtype::ANY, e) + +#### recursing into expression #### + +function abstract_call_gf(f::ANY, fargs, argtype::ANY, e, sv) argtypes = argtype.parameters - tm = _topmod((inference_stack::CallStack).sv) # TODO pass in sv instead + tm = _topmod(sv) if length(argtypes)>2 && argtypes[3]===Int && (argtypes[2] <: Tuple || (isa(argtypes[2], DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) && @@ -679,11 +812,11 @@ function abstract_call_gf(f::ANY, fargs, argtype::ANY, e) if istopfunction(tm, f, :promote_type) || istopfunction(tm, f, :typejoin) return Type end - return abstract_call_gf_by_type(f, argtype, e) + return abstract_call_gf_by_type(f, argtype, e, sv) end -function abstract_call_gf_by_type(f::ANY, argtype::ANY, e) - tm = _topmod((inference_stack::CallStack).sv) # TODO pass in sv instead +function abstract_call_gf_by_type(f::ANY, argtype::ANY, e, sv) + tm = _topmod(sv) # don't consider more than N methods. this trades off between # compiler performance and generated code performance. # typically, considering many methods means spending lots of time @@ -695,7 +828,7 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e) argtypes = argtype.parameters applicable = _methods_by_ftype(argtype, 4) rettype = Bottom - if is(applicable,false) + if is(applicable, false) # this means too many methods matched isa(e,Expr) && (e.head = :call) return Any @@ -712,7 +845,7 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e) sig = m[1] local linfo linfo = try - func_for_method(m[3],sig,m[2]) + func_for_method(m[3], sig, m[2]) catch NF end @@ -721,21 +854,72 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e) break end linfo = linfo::LambdaInfo + + # limit argument type tuple growth lsig = length(m[3].sig.parameters) - # limit argument type tuple based on size of definition signature. - # for example, given function f(T, Any...), limit to 3 arguments - # instead of the default (MAX_TUPLETYPE_LEN) - sp = inference_stack - limit = false - # look at the stack to detect recursive calls with growing argument lists - while sp !== EmptyCallStack() - if linfo.ast === sp.ast && length(argtypes) > length(sp.types.parameters) - limit = true; break + ls = length(sig.parameters) + # look at the existing edges to detect growing argument lists + limitlength = false + for (callee, _) in (sv.inf::InferenceState).edges + callee = callee::InferenceState + if linfo.def === callee.linfo.def && ls > length(callee.sv.atypes.parameters) + limitlength = true + break end - sp = sp.prev end - ls = length(sig.parameters) - if limit && ls > lsig+1 + + # limit argument type size growth + # TODO: FIXME: this heuristic depends on non-local state making type-inference unpredictable + for infstate in active + infstate === nothing && continue + infstate = infstate::InferenceState + if linfo.def === infstate.linfo.def + td = type_depth(sig) + if ls > length(infstate.sv.atypes.parameters) + limitlength = true + end + if td > type_depth(infstate.sv.atypes) + # impose limit if we recur and the argument types grow beyond MAX_TYPE_DEPTH + if td > MAX_TYPE_DEPTH + sig = limit_type_depth(sig, 0, true, []) + break + else + p1, p2 = sig.parameters, infstate.sv.atypes.parameters + if length(p2) == ls + limitdepth = false + newsig = Array(Any, ls) + for i = 1:ls + if p1[i] <: Function && type_depth(p1[i]) > type_depth(p2[i]) && + isa(p1[i],DataType) + # if a Function argument is growing (e.g. nested closures) + # then widen to the outermost function type. without this + # inference fails to terminate on do_quadgk. + newsig[i] = p1[i].name.primary + limitdepth = true + else + newsig[i] = limit_type_depth(p1[i], 1, true, []) + end + end + if limitdepth + sig = Tuple{newsig...} + break + end + end + end + end + end + end + +# # limit argument type size growth +# tdepth = type_depth(sig) +# if tdepth > MAX_TYPE_DEPTH +# sig = limit_type_depth(sig, 0, true, []) +# end + + # 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) + if limitlength && ls > lsig + 1 if !istopfunction(tm, f, :promote_typeof) fst = sig.parameters[lsig+1] allsame = true @@ -754,7 +938,7 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e) end end #print(m,"\n") - (_tree,rt) = typeinf(linfo, sig, m[2], linfo) + (_tree, rt) = typeinf_edge(linfo, sig, m[2], sv) rettype = tmerge(rettype, rt) if is(rettype,Any) break @@ -765,7 +949,7 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e) return rettype end -function invoke_tfunc(f::ANY, types::ANY, argtype::ANY) +function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::VarInfo) argtype = typeintersect(types,limit_tuple_type(argtype)) if is(argtype,Bottom) return Bottom @@ -787,14 +971,14 @@ function invoke_tfunc(f::ANY, types::ANY, argtype::ANY) if linfo === NF return Any end - return typeinf(linfo::LambdaInfo, ti, env, linfo)[2] + return typeinf_edge(linfo::LambdaInfo, ti, env, sv)[2] 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 # arrays of types, or `nothing`. -function precise_container_types(args, types, vtypes, sv) +function precise_container_types(args, types, vtypes::VarTable, sv) n = length(args) assert(n == length(types)) result = cell(n) @@ -830,7 +1014,7 @@ function precise_container_types(args, types, vtypes, sv) end # do apply(af, fargs...), where af is a function value -function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes, sv, e) +function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable, sv, e) ctypes = precise_container_types(fargs, aargtypes, vtypes, sv) if ctypes !== nothing # apply with known func with known tuple types @@ -908,10 +1092,7 @@ function pure_eval_call(f::ANY, fargs, argtypes::ANY, sv, e) return false end if !linfo.pure - typeinf(linfo, meth[1], meth[2], linfo) - if !linfo.pure - return false - end + return false end try @@ -923,7 +1104,7 @@ function pure_eval_call(f::ANY, fargs, argtypes::ANY, sv, e) end -function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes, sv::VarInfo, e) +function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, sv::VarInfo, e) t = pure_eval_call(f, fargs, argtypes, sv, e) t !== false && return t if is(f,_apply) && length(fargs)>1 @@ -954,7 +1135,7 @@ function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes, sv::VarInfo af = _ieval(af,sv) sig = argtypes[3] if isType(sig) && sig.parameters[1] <: Tuple - return invoke_tfunc(af, sig.parameters[1], Tuple{argtypes[4:end]...}) + return invoke_tfunc(af, sig.parameters[1], Tuple{argtypes[4:end]...}, sv) end end end @@ -978,10 +1159,10 @@ function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes, sv::VarInfo rt = builtin_tfunction(f, fargs, Tuple{argtypes[2:end]...}) return isa(rt, TypeVar) ? rt.ub : rt end - return abstract_call_gf(f, fargs, Tuple{argtypes...}, e) + return abstract_call_gf(f, fargs, Tuple{argtypes...}, e, sv) end -function abstract_eval_call(e, vtypes, sv::VarInfo) +function abstract_eval_call(e, vtypes::VarTable, sv::VarInfo) argtypes = Any[abstract_eval(a, vtypes, sv) for a in e.args] #print("call ", e.args[1], argtypes, "\n\n") for x in argtypes @@ -1003,7 +1184,7 @@ function abstract_eval_call(e, vtypes, sv::VarInfo) 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) + return abstract_call_gf_by_type(nothing, Tuple{argtypes...}, e, sv) end return Any end @@ -1018,7 +1199,7 @@ function abstract_eval_call(e, vtypes, sv::VarInfo) return abstract_call(f, e.args, argtypes, vtypes, sv, e) end -function abstract_eval(e::ANY, vtypes, sv::VarInfo) +function abstract_eval(e::ANY, vtypes::VarTable, sv::VarInfo) if isa(e,QuoteNode) v = (e::QuoteNode).value return type_typeof(v) @@ -1067,12 +1248,14 @@ function abstract_eval(e::ANY, vtypes, sv::VarInfo) if is(t,Bottom) # if we haven't gotten fed-back type info yet, return Bottom. otherwise # Bottom is the actual type of the variable, so return Type{Bottom}. - if haskey(sv.fedbackvars, var) + if get!(sv.fedbackvars, var, false) t = Type{Bottom} + else + sv.static_typeof = true end elseif isleaftype(t) t = Type{t} - elseif isleaftype(inference_stack.types) + elseif isleaftype(sv.atypes) if isa(t,TypeVar) t = Type{t.ub} else @@ -1139,7 +1322,7 @@ end function abstract_eval_symbol(s::Symbol, vtypes::ObjectIdDict, sv::VarInfo) t = get(vtypes,s,NF) if is(t,NF) - sp = sv.linfo.sparam_syms + sp = sv.inf.linfo.sparam_syms for i=1:length(sp) if is(sp[i],s) # static parameter @@ -1167,7 +1350,8 @@ function abstract_eval_symbol(s::Symbol, vtypes::ObjectIdDict, sv::VarInfo) return t.typ end -typealias VarTable ObjectIdDict + +#### handling for statement-position expressions #### type StateUpdate var::Union{Symbol,GenSym} @@ -1182,11 +1366,12 @@ function getindex(x::StateUpdate, s::Symbol) return get(x.state,s,NF) end -function abstract_interpret(e::ANY, vtypes, sv::VarInfo) +function abstract_interpret(e::ANY, vtypes::VarTable, sv::VarInfo) !isa(e,Expr) && return vtypes # handle assignment if is(e.head,:(=)) t = abstract_eval(e.args[2], vtypes, sv) + t === Bottom && return () lhs = e.args[1] if isa(lhs,SymbolNode) lhs = lhs.name @@ -1196,9 +1381,11 @@ function abstract_interpret(e::ANY, vtypes, sv::VarInfo) return StateUpdate(lhs, VarState(t,false), vtypes) end elseif is(e.head,:call) - abstract_eval(e, vtypes, sv) + t = abstract_eval(e, vtypes, sv) + t === Bottom && return () elseif is(e.head,:gotoifnot) - abstract_eval(e.args[1], vtypes, sv) + t = abstract_eval(e.args[1], vtypes, sv) + t === Bottom && return () elseif is(e.head,:method) fname = e.args[1] if isa(fname,Symbol) @@ -1265,7 +1452,7 @@ schanged(n::ANY, o::ANY) = is(o,NF) || (!is(n,NF) && !issubstate(n, o)) stupdate(state::Tuple{}, changes::VarTable, vars) = copy(changes) stupdate(state::Tuple{}, changes::StateUpdate, vars) = stupdate(ObjectIdDict(), changes, vars) -function stupdate(state::ObjectIdDict, changes::Union{StateUpdate,VarTable}, vars) +function stupdate(state::VarTable, changes::Union{StateUpdate,VarTable}, vars) for i = 1:length(vars) v = vars[i] newtype = changes[v] @@ -1289,6 +1476,9 @@ function stchanged(new::Union{StateUpdate,VarTable}, old, vars) return false end + +#### helper functions for typeinf initialization and looping #### + function findlabel(labels, l) i = l+1 > length(labels) ? 0 : labels[l+1] if i == 0 @@ -1355,490 +1545,461 @@ f_argnames(ast) = is_rest_arg(arg::ANY) = (ccall(:jl_is_rest_arg,Int32,(Any,), arg) != 0) -function typeinf_ext(linfo, atypes::ANY, def) - global inference_stack - last = inference_stack - inference_stack = EmptyCallStack() - result = typeinf(linfo, atypes, svec(), def, true, true) - inference_stack = last - return result -end -typeinf(linfo,atypes::ANY,sparams::ANY) = typeinf(linfo,atypes,sparams,linfo,true,false) -typeinf(linfo,atypes::ANY,sparams::ANY,def) = typeinf(linfo,atypes,sparams,def,true,false) +#### entry points for inferring a LambdaInfo given a type signature #### -CYCLE_ID = 1 - -#trace_inf = false -#enable_trace_inf(on) = (global trace_inf=on) - -# def is the original unspecialized version of a method. we aggregate all -# saved type inference data there. -function typeinf(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, def, cop, needtree) +function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, needtree::Bool, optimize::Bool, cached::Bool, caller) + #println(linfo) if linfo.module === Core && isempty(sparams) && isempty(linfo.sparam_vals) atypes = Tuple end - #dbg = - #dotrace = true local ast::Expr, tfunc_idx = -1 curtype = Bottom - redo = false # check cached t-functions - tf = def.tfunc - if !is(tf,nothing) + # linfo.def is the original unspecialized version of a method. + # we aggregate all saved type inference data there. + tf = linfo.def.tfunc + if cached && !is(tf, nothing) tfarr = tf::Array{Any,1} for i = 1:3:length(tfarr) - if typeseq(tfarr[i],atypes) - code = tfarr[i+1] - if tfarr[i+2] - redo = true - tfunc_idx = i+1 - curtype = code - break - end - if isa(code,Type) + if typeseq(tfarr[i], atypes) + code = tfarr[i + 1] + if isa(code, InferenceState) + # inference on this signature is in progress + frame = code + tfunc_idx = i + if linfo.inInference + # record the LambdaInfo where this result should be cached when it is finished + @assert !frame.linfo.inInference + frame.linfo = linfo + end + elseif isa(code, Type) curtype = code::Type # sometimes just a return type is stored here. if a full AST # is not needed, we can return it. if !needtree - return (nothing, code) + return (nothing, code, true) end else - return code # else code is a tuple (ast, type) - end - end - end - end - # TODO: typeinf currently gets stuck without this - if linfo.name === :abstract_interpret || linfo.name === :alloc_elim_pass || linfo.name === :abstract_call_gf - return (linfo.ast, Any) - end - - (fulltree, result, rec) = typeinf_uncached(linfo, atypes, sparams, def, curtype, cop, true) - if fulltree === () - return (fulltree, result::Type) - end - - if !redo - if is(def.tfunc,nothing) - def.tfunc = Any[] - end - tfarr = def.tfunc::Array{Any,1} - idx = -1 - for i = 1:3:length(tfarr) - if typeseq(tfarr[i],atypes) - idx = i; break - end - end - if idx == -1 - l = length(tfarr) - idx = l+1 - resize!(tfarr, l+3) - end - tfarr[idx] = atypes - # in the "rec" state this tree will not be used again, so store - # just the return type in place of it. - tfarr[idx+1] = rec ? result : (fulltree,result) - tfarr[idx+2] = rec - else - def.tfunc[tfunc_idx] = rec ? result : (fulltree,result) - def.tfunc[tfunc_idx+1] = rec - end - - return (fulltree, result::Type) -end - -typeinf_uncached(linfo, atypes::ANY, sparams::ANY; optimize=true) = - typeinf_uncached(linfo, atypes, sparams, linfo, Bottom, true, optimize) - -# t[n:end] -tupletype_tail(t::ANY, n) = Tuple{t.parameters[n:end]...} - -# compute an inferred (optionally optimized) AST without global effects (i.e. updating the cache) -function typeinf_uncached(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, def, curtype, cop, optimize) - ast0 = def.ast - #if dbg - # print("typeinf ", linfo.name, " ", object_id(ast0), "\n") - #end - # if isdefined(:STDOUT) - # write(STDOUT, "typeinf ") - # write(STDOUT, string(linfo.name)) - # write(STDOUT, string(atypes)) - # write(STDOUT, '\n') - # end - #print("typeinf ", ast0, " ", sparams, " ", atypes, "\n") - - global inference_stack, CYCLE_ID - - f = inference_stack - while !isa(f,EmptyCallStack) - if is(f.ast,ast0) - # impose limit if we recur and the argument types grow beyond MAX_TYPE_DEPTH - td = type_depth(atypes) - if td > type_depth(f.types) - if td > MAX_TYPE_DEPTH - atypes = limit_type_depth(atypes, 0, true, []) - break - else - p1, p2 = atypes.parameters, f.types.parameters - n = length(p1) - if length(p2) == n - limited = false - newatypes = Array(Any, n) - for i = 1:n - if p1[i] <: Function && type_depth(p1[i]) > type_depth(p2[i]) && - isa(p1[i],DataType) - # if a Function argument is growing (e.g. nested closures) - # then widen to the outermost function type. without this - # inference fails to terminate on do_quadgk. - newatypes[i] = p1[i].name.primary - limited = true - else - newatypes[i] = p1[i] - end - end - if limited - atypes = Tuple{newatypes...} - break - end + inftree, inftyp = code + if linfo.inInference + linfo.inferred = true + linfo.ast = inftree + linfo.rettype = inftyp + linfo.inInference = false end + return (inftree, inftyp, true) # code is a tuple (ast, type) end end end - f = f.prev end - # check for recursion - f = inference_stack - while !isa(f,EmptyCallStack) - if (is(f.ast,ast0) || f.ast==ast0) && typeseq(f.types, atypes) - # return best guess so far - (f::CallStack).recurred = true - (f::CallStack).cycleid = CYCLE_ID - r = inference_stack - while !is(r, f) - # mark all frames that are part of the cycle - r.recurred = true - r.cycleid = CYCLE_ID - r = r.prev - end - CYCLE_ID += 1 - #print("*==> ", f.result,"\n") - return ((),f.result,true) + if tfunc_idx == -1 + if caller === nothing && needtree && in_typeinf_loop + # if the caller needed the ast, but we are already in the typeinf loop + # then just return early -- we can't fulfill this request + # if the client was typeinf_ext(toplevel), then we cancel the inInference request + # if the client was inlining, then this means we decided not to try to infer this + # particular signature (due to signature coarsening in abstract_call_gf_by_type) + # and attempting to force it now would be a bad idea (non terminating) + linfo.inInference = false + return (nothing, Union{}, false) end - f = f.prev - end - - #if trace_inf - # print("typeinf ", linfo.name, " ", atypes, " ", linfo.file,":",linfo.line,"\n") - #end - - #if dbg print("typeinf ", linfo.name, " ", atypes, "\n") end - - if cop + # add lam to be inferred and record the edge ast = ccall(:jl_prepare_ast, Any, (Any,), linfo)::Expr - else - ast = linfo.ast - end + sv = VarInfo(linfo, atypes, ast) - sv = VarInfo(linfo, ast) - - if length(linfo.sparam_vals) > 0 - # handled by VarInfo constructor - elseif isempty(sparams) && !isempty(linfo.sparam_syms) - sv.sp = svec(Any[ TypeVar(sym, Any, true) for sym in linfo.sparam_syms ]...) - else - sv.sp = sparams - end - - args = f_argnames(ast) - la = length(args) - assert(is(ast.head,:lambda)) - vinflist = sv.vinfo - vars = sv.vars - body = (ast.args[3].args)::Array{Any,1} - n = length(body) - - labels = zeros(Int, sv.label_counter) - for i=1:length(body) - b = body[i] - if isa(b,LabelNode) - labels[b.label+1] = i + if length(linfo.sparam_vals) > 0 + # handled by VarInfo constructor + elseif isempty(sparams) && !isempty(linfo.sparam_syms) + sv.sp = svec(Any[ TypeVar(sym, Any, true) for sym in linfo.sparam_syms ]...) + else + sv.sp = sparams end - end - # our stack frame - frame = CallStack(ast0, atypes, inference_stack) - frame.sv = sv - inference_stack = frame - frame.result = curtype + # our stack frame inference context + frame = InferenceState(sv, linfo, optimize) + push!(workq, frame) + frame.inworkq = true - rec = false - toprec = false + if cached + #println(linfo) + #println(atypes) - s = Any[ () for i=1:n ] - # initial types - s[1] = ObjectIdDict() - for v in vars - s[1][v] = VarState(Bottom,true) - end - if la > 0 - lastarg = ast.args[1][la] - if is_rest_arg(lastarg) - if atypes === Tuple - if la > 1 - atypes = Tuple{Any[Any for i=1:la-1]..., Tuple.parameters[1]} - end - s[1][args[la]] = VarState(Tuple,false) - else - s[1][args[la]] = VarState(limit_tuple_depth(tupletype_tail(atypes,la)),false) - end - la -= 1 - else - if atypes === Tuple - atypes = Tuple{Any[Any for i=1:la]..., Tuple.parameters[1]} + if is(linfo.def.tfunc, nothing) + linfo.def.tfunc = Any[] end + tfarr = linfo.def.tfunc::Array{Any,1} + l = length(tfarr) + tfunc_idx = l + 1 + resize!(tfarr, l + 3) + tfarr[tfunc_idx] = atypes + tfarr[tfunc_idx + 1] = frame + tfarr[tfunc_idx + 2] = false + frame.tfunc_idx = tfunc_idx end end - laty = length(atypes.parameters) - if laty > 0 - lastatype = atypes.parameters[laty] - if isvarargtype(lastatype) - lastatype = lastatype.parameters[1] - laty -= 1 - end - if isa(lastatype, TypeVar) - lastatype = lastatype.ub - end - if laty > la - laty = la - end - for i=1:laty - atyp = atypes.parameters[i] - if isa(atyp, TypeVar) - atyp = atyp.ub + if caller !== nothing + me = caller.inf + if haskey(me.edges, frame) + Ws = me.edges[frame]::Vector{Int} + if !(caller.currpc in Ws) + push!(Ws, caller.currpc) end - s[1][args[i]] = VarState(atyp, false) - end - for i=laty+1:la - s[1][args[i]] = VarState(lastatype, false) + else + @assert caller.currpc > 0 + Ws = Int[caller.currpc] + me.edges[frame] = Ws + push!(frame.backedges, (me, Ws)) end - elseif la != 0 - return ((), Bottom, false) # wrong number of arguments - end - - gensym_uses = find_gensym_uses(body) - if length(sv.gensym_types) != length(gensym_uses) - sv.gensym_types = Any[ NF for i=1:length(gensym_uses) ] end - gensym_types = sv.gensym_types - gensym_init = copy(gensym_types) - - recpts = IntSet() # statements that depend recursively on our value - W = IntSet() - - @label typeinf_top - - typegotoredo = false + typeinf_loop() + return (frame.sv.ast, frame.bestguess, frame.inferred) +end - # exception handlers - cur_hand = () - handler_at = Any[ () for i=1:n ] - n_handlers = 0 +function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, caller) + return typeinf_edge(linfo, atypes, sparams, false, true, true, caller) +end +function typeinf(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, needtree::Bool=false) + return typeinf_edge(linfo, atypes, sparams, needtree, true, true, nothing) +end +# compute an inferred (optionally optimized) AST without global effects (i.e. updating the cache) +function typeinf_uncached(linfo::LambdaInfo, atypes::ANY, sparams::ANY; optimize::Bool=true) + return typeinf_edge(linfo, atypes, sparams, true, optimize, false, nothing) +end +function typeinf_uncached(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, optimize::Bool) + return typeinf_edge(linfo, atypes, sparams, true, optimize, false, nothing) +end +function typeinf_ext(linfo::LambdaInfo, toplevel::Bool) + return typeinf_edge(linfo, linfo.specTypes, svec(), toplevel, true, true, nothing) +end - push!(W,1) #initial pc to visit - while !isempty(W) - pc = first(W) - while true - #print(pc,": ",s[pc],"\n") - delete!(W, pc) - if is(handler_at[pc],()) - handler_at[pc] = cur_hand - else - cur_hand = handler_at[pc] - end - stmt = body[pc] - changes = abstract_interpret(stmt, s[pc]::ObjectIdDict, sv) - if frame.recurred - rec = true - if !(isa(frame.prev,CallStack) && frame.prev.cycleid == frame.cycleid) - toprec = true +in_typeinf_loop = false +function typeinf_loop() + global in_typeinf_loop + if in_typeinf_loop + return + end + in_typeinf_loop = true + # the core type-inference algorithm + # processes everything in workq, + # and returns when there is nothing left + while nactive[] > 0 + while active[end] === nothing + pop!(active) + end + if isempty(workq) + frame = active[end]::InferenceState + else + frame = pop!(workq) + frame.inworkq = false + end + global global_sv = frame.sv # TODO: actually pass this to all functions that need it + W = frame.ip + s = frame.stmt_types + n = frame.nstmts + while !isempty(W) + # make progress on the active ip set + local pc::Int = first(W), pc´::Int + while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 + #print(pc,": ",s[pc],"\n") + delete!(W, pc) + frame.sv.currpc = pc + frame.sv.static_typeof = false + if frame.handler_at[pc] === 0 + frame.handler_at[pc] = frame.cur_hand + else + frame.cur_hand = frame.handler_at[pc] end - push!(recpts, pc) - #if dbg - # show(pc); print(" recurred\n") - #end - frame.recurred = false - end - if !is(cur_hand,()) - # propagate type info to exception handler - l = cur_hand[1]::Int - if stchanged(changes, s[l], vars) - push!(W, l) - s[l] = stupdate(s[l], changes, vars) + stmt = frame.sv.body[pc] + changes = abstract_interpret(stmt, s[pc]::ObjectIdDict, frame.sv) + if changes === () + # if there was a Expr(:static_typeof) on this line, + # need to continue to the next pc even though the return type was Bottom + # otherwise, this line threw an error and there is no need to continue + frame.sv.static_typeof || break + changes = s[pc] end - end - pc´ = pc+1 - if isa(changes,StateUpdate) && isa((changes::StateUpdate).var, GenSym) - changes = changes::StateUpdate - id = (changes.var::GenSym).id+1 - new = changes.vtype.typ - old = gensym_types[id] - if old===NF || !(new <: old) - gensym_types[id] = tmerge(old, new) - for r in gensym_uses[id] - if !is(s[r],()) # s[r] === () => unreached statement - push!(W, r) - end + if frame.cur_hand !== () + # propagate type info to exception handler + l = frame.cur_hand[1] + if stchanged(changes, s[l], frame.sv.vars) + push!(W, l) + s[l] = stupdate(s[l], changes, frame.sv.vars) end end - elseif isa(stmt,GotoNode) - pc´ = findlabel(labels,stmt.label) - elseif isa(stmt,Expr) - hd = stmt.head - if is(hd,:gotoifnot) - condexpr = stmt.args[1] - l = findlabel(labels,stmt.args[2]) - # constant conditions - if is(condexpr,true) - elseif is(condexpr,false) - pc´ = l - else - # general case - handler_at[l] = cur_hand - if stchanged(changes, s[l], vars) - push!(W, l) - s[l] = stupdate(s[l], changes, vars) + pc´ = pc+1 + if isa(changes, StateUpdate) && isa((changes::StateUpdate).var, GenSym) + # directly forward changes to a GenSym to the applicable line + changes = changes::StateUpdate + id = (changes.var::GenSym).id + 1 + new = changes.vtype.typ + old = frame.sv.gensym_types[id] + if old===NF || !(new <: old) + frame.sv.gensym_types[id] = tmerge(old, new) + for r in frame.gensym_uses[id] + if !is(s[r], ()) # s[r] === () => unreached statement + push!(W, r) + end end end - elseif is(hd,:type_goto) - for i = 2:length(stmt.args) - var = stmt.args[i]::GenSym - # Store types that need to be fed back via type_goto - # in gensym_init. After finishing inference, if any - # of these types changed, start over with the fed-back - # types known from the beginning. - # See issue #3821 (using !typeseq instead of !subtype), - # and issue #7810. - id = var.id+1 - vt = gensym_types[id] - ot = gensym_init[id] - if ot===NF || !typeseq(vt,ot) - gensym_init[id] = vt - typegotoredo = true + elseif isa(stmt, GotoNode) + pc´ = findlabel(frame.labels, (stmt::GotoNode).label) + elseif isa(stmt, Expr) + stmt = stmt::Expr + hd = stmt.head + if is(hd, :gotoifnot) + condexpr = stmt.args[1] + l = findlabel(frame.labels, stmt.args[2]::Int) + # constant conditions + if is(condexpr, true) + elseif is(condexpr, false) + pc´ = l + else + # general case + frame.handler_at[l] = frame.cur_hand + if stchanged(changes, s[l], frame.sv.vars) + # add else branch to active IP list + push!(W, l) + s[l] = stupdate(s[l], changes, frame.sv.vars) + end end - sv.fedbackvars[var] = true - end - elseif is(hd,:return) - pc´ = n+1 - rt = abstract_eval(stmt.args[1], s[pc], sv) - if frame.recurred - rec = true - if !(isa(frame.prev,CallStack) && frame.prev.cycleid == frame.cycleid) - toprec = true + elseif is(hd, :type_goto) + for i = 2:length(stmt.args) + var = stmt.args[i]::GenSym + # Store types that need to be fed back via type_goto + # in gensym_init. After finishing inference, if any + # of these types changed, start over with the fed-back + # types known from the beginning. + # See issue #3821 (using !typeseq instead of !subtype), + # and issue #7810. + id = var.id+1 + vt = frame.sv.gensym_types[id] + ot = frame.gensym_init[id] + if ot===NF || !typeseq(vt, ot) + frame.gensym_init[id] = vt + if get(frame.sv.fedbackvars, var, false) + frame.typegotoredo = true + end + end + frame.sv.fedbackvars[var] = true end - push!(recpts, pc) - #if dbg - # show(pc); print(" recurred\n") - #end - frame.recurred = false - end - #if dbg - # print("at "); show(pc) - # print(" result is "); show(frame.result) - # print(" and rt is "); show(rt) - # print("\n") - #end - if tchanged(rt, frame.result) - frame.result = tmerge(frame.result, rt) - # revisit states that recursively depend on this - for r in recpts - #if dbg - # print("will revisit ") - # show(r) - # print("\n") - #end - push!(W, r) + elseif is(hd, :return) + pc´ = n + 1 + rt = abstract_eval(stmt.args[1], s[pc], frame.sv) + if tchanged(rt, frame.bestguess) + # new (wider) return type for frame + frame.bestguess = tmerge(frame.bestguess, rt) + for (caller, callerW) in frame.backedges + # notify backedges of updated type information + for caller_pc in callerW + push!(caller.ip, caller_pc) + end + end + unmark_fixedpoint(frame) + end + elseif is(hd, :enter) + l = findlabel(frame.labels, stmt.args[1]::Int) + frame.cur_hand = (l, frame.cur_hand) + # propagate type info to exception handler + l = frame.cur_hand[1] + old = s[l] + new = s[pc]::ObjectIdDict + if old === () || stchanged(new, old::ObjectIdDict, frame.sv.vars) + push!(W, l) + s[l] = stupdate(old, new, frame.sv.vars) + end +# if frame.handler_at[l] === 0 +# frame.n_handlers += 1 +# if frame.n_handlers > 25 +# # too many exception handlers slows down inference a lot. +# # for an example see test/libgit2.jl on 0.5-pre master +# # around e.g. commit c072d1ce73345e153e4fddf656cda544013b1219 +# inference_stack = (inference_stack::CallStack).prev +# return (ast0, Any, false) +# end +# end + frame.handler_at[l] = frame.cur_hand + elseif is(hd, :leave) + for i = 1:((stmt.args[1])::Int) + frame.cur_hand = frame.cur_hand[2] end end - elseif is(hd,:enter) - l = findlabel(labels,stmt.args[1]::Int) - cur_hand = (l,cur_hand) - if handler_at[l] === () - n_handlers += 1 - if n_handlers > 25 - # too many exception handlers slows down inference a lot. - # for an example see test/libgit2.jl on 0.5-pre master - # around e.g. commit c072d1ce73345e153e4fddf656cda544013b1219 - inference_stack = (inference_stack::CallStack).prev - return (ast0, Any, false) + end + if pc´<=n && (frame.handler_at[pc´] = frame.cur_hand; true) && + stchanged(changes, s[pc´], frame.sv.vars) + s[pc´] = stupdate(s[pc´], changes, frame.sv.vars) + pc = pc´ + elseif pc´ in W + pc = pc´ + else + break + end + end + end + + # with no active ip's, type inference on frame is done if there are no outstanding (unfinished) edges + finished = isempty(frame.edges) + if isempty(workq) + # oops, there's a cycle somewhere in the `edges` graph + # so we've run out off the tree and will need to start work on the loop + frame.fixedpoint = true + end + + restart = false + if finished || frame.fixedpoint + if frame.typegotoredo + # if any type_gotos changed, clear state and restart. + frame.typegotoredo = false + for ll = 2:length(s) + s[ll] = () + end + empty!(W) + push!(W, 1) + frame.cur_hand = () + frame.handler_at = Any[ () for i=1:n ] + frame.n_handlers = 0 + frame.sv.gensym_types[:] = frame.gensym_init + restart = true + else + # if a static_typeof was never reached, + # use Union{} as its real type and continue + # running type inference from its uses + # (one of which is the static_typeof) + # TODO: this restart should happen just before calling finish() + for (fbvar, seen) in frame.sv.fedbackvars + if !seen + frame.sv.fedbackvars[fbvar] = true + id = (fbvar::GenSym).id + 1 + for r in frame.gensym_uses[id] + if !is(s[r], ()) # s[r] === () => unreached statement + push!(W, r) + end end + restart = true end - handler_at[l] = cur_hand - elseif is(hd,:leave) - for i=1:((stmt.args[1])::Int) - cur_hand = cur_hand[2] + end + end + + if restart + push!(workq, frame) + frame.inworkq = true + elseif finished + finish(frame) + else # fixedpoint propagation + for (i,_) in frame.edges + i = i::InferenceState + if !i.fixedpoint + i.inworkq || push!(workq, i) + i.inworkq = true + i.fixedpoint = true end end end - if pc´<=n && (handler_at[pc´] = cur_hand; true) && - stchanged(changes, s[pc´], vars) - s[pc´] = stupdate(s[pc´], changes, vars) - pc = pc´ - elseif pc´ in W - pc = pc´ - else - break + end + if !restart && isempty(workq) && nactive[] > 0 + # nothing in active has an edge that hasn't reached a fixed-point + # so all of them can be considered finished now + for i in active + i === nothing && continue + i = i::InferenceState + i.fixedpoint && finish(i) end end end + # cleanup the active queue + empty!(active) +# while active[end] === nothing +# # this pops everything, but with exaggerated care just in case +# # something managed to add something to the queue at the same time +# # (or someone decides to use an alternative termination condition) +# pop!(active) +# end + in_typeinf_loop = false + nothing +end - if typegotoredo - # if any type_gotos changed, clear state and restart. - for ll = 2:length(s) - s[ll] = () +function unmark_fixedpoint(frame::InferenceState) + # type information changed for frame, so its edges are no longer stuck + # recursively unmark any nodes that had previously been thought to be at a fixedpoint + # based upon (recursively) assuming that frame was stuck + if frame.fixedpoint + frame.fixedpoint = false + for (i,_) in frame.backedges + unmark_fixedpoint(i) end - empty!(W) - gensym_types[:] = gensym_init - frame.result = curtype - @goto typeinf_top end - for i = 1:length(gensym_types) - if gensym_types[i] === NF - gensym_types[i] = Union{} - end +end + + +#### finalize and record the result of running type inference #### + +# inference completed on `me` +# update the LambdaInfo and notify the edges +function finish(me::InferenceState) + # lazy-delete the item from active for several reasons: + # efficiency, correctness, and recursion-safety + nactive[] -= 1 + active[findlast(active, me)] = nothing + for (i,_) in me.edges + @assert (i::InferenceState).fixedpoint end - #print("\n",ast,"\n") - #if dbg print("==> ", frame.result,"\n") end - if (toprec && typeseq(curtype, frame.result)) || !isa(frame.prev,CallStack) - rec = false + # annotate fulltree with type information + for i = 1:length(me.sv.gensym_types) + if me.sv.gensym_types[i] === NF + me.sv.gensym_types[i] = Union{} + end end - fulltree = type_annotate(ast, s, sv, frame.result, args) + fulltree = type_annotate(me.sv.ast, me.stmt_types, me.sv, me.bestguess, me.args) - if !rec - @assert fulltree.args[3].head === :body - if optimize - if JLOptions().can_inline == 1 - fulltree.args[3] = inlining_pass(fulltree.args[3], sv, fulltree)[1] - # inlining can add variables - sv.vars = append_any(f_argnames(fulltree), map(vi->vi[1], fulltree.args[2][1])) - inbounds_meta_elim_pass(fulltree.args[3]) - end - alloc_elim_pass(fulltree, sv) - getfield_elim_pass(fulltree.args[3], sv) + # make sure (meta pure) is stripped from full tree + @assert fulltree.args[3].head === :body + body = Expr(:block) + body.args = fulltree.args[3].args::Array{Any,1} + ispure = popmeta!(body, :pure)[1] + + # run optimization passes on fulltree + if me.optimize + if JLOptions().can_inline == 1 + fulltree.args[3] = inlining_pass(fulltree.args[3], me.sv, fulltree)[1] + # inlining can add variables + me.sv.vars = append_any(f_argnames(fulltree), map(vi->vi[1], fulltree.args[2][1])) + inbounds_meta_elim_pass(fulltree.args[3]) end - linfo.inferred = true - body = Expr(:block) - body.args = fulltree.args[3].args::Array{Any,1} - linfo.pure = popmeta!(body, :pure)[1] - fulltree = ccall(:jl_compress_ast, Any, (Any,Any), def, fulltree) + alloc_elim_pass(fulltree, me.sv) + getfield_elim_pass(fulltree.args[3], me.sv) end - inference_stack = (inference_stack::CallStack).prev - return (fulltree, frame.result, rec) + # finalize and record the linfo result + me.sv.ast = fulltree + me.inferred = true + + compressedtree = ccall(:jl_compress_ast, Any, (Any,Any), me.linfo.def, fulltree) + if me.linfo.inInference + me.linfo.inferred = true + me.linfo.pure = ispure + me.linfo.ast = compressedtree + me.linfo.rettype = me.bestguess + me.linfo.inInference = false + end + if me.tfunc_idx != -1 + me.linfo.def.tfunc[me.tfunc_idx + 1] = (compressedtree, me.bestguess) + me.linfo.def.tfunc[me.tfunc_idx + 2] = false + end + + # update all of the callers by traversing the backedges + for (i,_) in me.backedges + if !me.fixedpoint || !i.fixedpoint + # wake up each backedge, unless both me and it already reached a fixed-point (cycle resolution stage) + delete!(i.edges, me) + i.inworkq || push!(workq, i) + i.inworkq = true + end + end + nothing end function record_var_type(e::Symbol, t::ANY, decls) @@ -2203,6 +2364,9 @@ function ast_localvars(ast) locals end + +#### post-inference optimizations #### + # inline functions whose bodies are "inline_worthy" # where the function body doesn't contain any argument more than once. # static parameters are ok if all the static parameter values are leaf types, @@ -2349,8 +2513,8 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::VarInfo, methargs = metharg.parameters nm = length(methargs) - (ast, ty) = typeinf(linfo, metharg, methsp, linfo, true, true) - if is(ast,()) + (ast, ty, inferred) = typeinf(linfo, metharg, methsp, true) + if is(ast,nothing) || !inferred return NF end needcopy = true @@ -3388,4 +3552,16 @@ end #tfunc(f,t) = methods(f,t)[1].func.code.tfunc + +#### bootstrapping #### + +# make sure that typeinf is executed before turning on typeinf_ext +# this ensures that typeinf_ext doesn't recurse before it can add the item to the workq +for m in _methods_by_ftype(Tuple{typeof(typeinf_loop), Vararg{Any}}, 10) + typeinf(m[3].func, m[1], m[2], true) +end +for m in _methods_by_ftype(Tuple{typeof(typeinf_edge), Vararg{Any}}, 10) + typeinf(m[3].func, m[1], m[2], true) +end + ccall(:jl_set_typeinf_func, Void, (Any,), typeinf_ext) diff --git a/base/int.jl b/base/int.jl index db5207dbe66d5..9ad33f5fa3d07 100644 --- a/base/int.jl +++ b/base/int.jl @@ -415,5 +415,3 @@ else rem(x::Int128, y::Int128) = box(Int128,checked_srem_int(unbox(Int128,x),unbox(Int128,y))) rem(x::UInt128, y::UInt128) = box(UInt128,checked_urem_int(unbox(UInt128,x),unbox(UInt128,y))) end - -(::Type{T}){T}(arg) = convert(T, arg)::T diff --git a/base/reflection.jl b/base/reflection.jl index b24a2c7acbca1..633fd41f937da 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -295,11 +295,9 @@ function code_typed(f::ANY, types::ANY=Tuple; optimize=true) for x in _methods(f,types,-1) linfo = func_for_method_checked(x, types) if optimize - (tree, ty) = Core.Inference.typeinf(linfo, x[1], x[2], linfo, - true, true) + (tree, ty) = Core.Inference.typeinf(linfo, x[1], x[2], true) else - (tree, ty) = Core.Inference.typeinf_uncached(linfo, x[1], x[2], - optimize=false) + (tree, ty) = Core.Inference.typeinf_uncached(linfo, x[1], x[2], optimize=false) end if !isa(tree, Expr) tree = ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, tree) diff --git a/base/sysimg.jl b/base/sysimg.jl index 61ea07580c8f3..0090bda16acf7 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -48,6 +48,7 @@ include("int.jl") include("operators.jl") include("pointer.jl") include("refpointer.jl") +(::Type{T}){T}(arg) = convert(T, arg)::T include("functors.jl") include("checked.jl") importall .Checked @@ -58,27 +59,23 @@ include("subarray.jl") include("array.jl") # Array convenience converting constructors -(::Type{Array{T}}){T}(m::Integer) = Array{T}(Int(m)) -(::Type{Array{T}}){T}(m::Integer, n::Integer) = Array{T}(Int(m), Int(n)) -(::Type{Array{T}}){T}(m::Integer, n::Integer, o::Integer) = Array{T}(Int(m), Int(n), Int(o)) +(::Type{Array{T}}){T}(m::Integer) = Array{T,1}(Int(m)) +(::Type{Array{T}}){T}(m::Integer, n::Integer) = Array{T,2}(Int(m), Int(n)) +(::Type{Array{T}}){T}(m::Integer, n::Integer, o::Integer) = Array{T,3}(Int(m), Int(n), Int(o)) (::Type{Array{T}}){T}(d::Integer...) = Array{T}(convert(Tuple{Vararg{Int}}, d)) -(::Type{Vector{T}}){T}(m::Integer) = Array{T}(m) -(::Type{Vector{T}}){T}() = Array{T}(0) -(::Type{Vector})(m::Integer) = Array{Any}(m) -(::Type{Vector})() = Array{Any}(0) - -(::Type{Matrix{T}}){T}(m::Integer, n::Integer) = Array{T}(m, n) -(::Type{Matrix{T}}){T}() = Array{T}(0, 0) -(::Type{Matrix})(m::Integer, n::Integer) = Array{Any}(m, n) -(::Type{Matrix})() = Array{Any}(0, 0) +(::Type{Vector})() = Array{Any,1}(0) +(::Type{Vector{T}}){T}(m::Integer) = Array{T,1}(Int(m)) +(::Type{Vector})(m::Integer) = Array{Any,1}(Int(m)) +(::Type{Matrix})() = Array{Any,2}(0, 0) +(::Type{Matrix{T}}){T}(m::Integer, n::Integer) = Matrix{T}(Int(m), Int(n)) +(::Type{Matrix})(m::Integer, n::Integer) = Matrix{Any}(Int(m), Int(n)) # TODO: possibly turn these into deprecations -Array{T,N}(::Type{T}, d::NTuple{N,Int}) = Array{T}(d) Array{T}(::Type{T}, d::Integer...) = Array{T}(convert(Tuple{Vararg{Int}}, d)) -Array{T}(::Type{T}, m::Integer) = Array{T}(m) -Array{T}(::Type{T}, m::Integer,n::Integer) = Array{T}(m,n) -Array{T}(::Type{T}, m::Integer,n::Integer,o::Integer) = Array{T}(m,n,o) +Array{T}(::Type{T}, m::Integer) = Array{T,1}(Int(m)) +Array{T}(::Type{T}, m::Integer,n::Integer) = Array{T,2}(Int(m),Int(n)) +Array{T}(::Type{T}, m::Integer,n::Integer,o::Integer) = Array{T,3}(Int(m),Int(n),Int(o)) # numeric operations include("hashing.jl") diff --git a/src/cgutils.cpp b/src/cgutils.cpp index d4e1cdfcda535..2f3e1565e9ac4 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -235,10 +235,6 @@ static inline void add_named_global(GlobalValue *gv, T *_addr, bool dllimport = // --- string constants --- static std::map stringConstants; -extern "C" { - extern int jl_in_inference; -} - #if defined(USE_MCJIT) || defined(USE_ORCJIT) static GlobalVariable *global_proto(GlobalVariable *G) { @@ -261,7 +257,7 @@ static GlobalVariable *stringConst(const std::string &txt) // in inference, we can not share string constants between // modules as there might be multiple compiles on the stack // with calls in between them. - if (gv == NULL || jl_in_inference) { + if (gv == NULL) { std::stringstream ssno; std::string vname; ssno << strno; diff --git a/src/gf.c b/src/gf.c index 71cade9922094..444fa378fccb5 100644 --- a/src/gf.c +++ b/src/gf.c @@ -392,44 +392,29 @@ jl_lambda_info_t *jl_method_cache_insert(jl_methtable_t *mt, jl_tupletype_t *typ /* run type inference on lambda "li" in-place, for given argument types. "def" is the original method definition of which this is an instance; - can be equal to "li" if not applicable. + can be equal to "li->def" if not applicable. */ -int jl_in_inference = 0; -void jl_type_infer(jl_lambda_info_t *li, jl_lambda_info_t *def) +void jl_type_infer(jl_lambda_info_t *li, jl_value_t *toplevel) { - JL_LOCK(codegen); // Might GC - int last_ii = jl_in_inference; - jl_in_inference = 1; - if (jl_typeinf_func != NULL) { - // TODO: this should be done right before code gen, so if it is - // interrupted we can try again the next time the function is - // called +#ifdef ENABLE_INFERENCE + if (jl_typeinf_func != NULL && li->module != jl_gf_mtable(jl_typeinf_func)->module) { + JL_LOCK(codegen); // Might GC assert(li->inInference == 0); li->inInference = 1; - jl_value_t *fargs[4]; + jl_value_t *fargs[3]; fargs[0] = (jl_value_t*)jl_typeinf_func; fargs[1] = (jl_value_t*)li; - fargs[2] = (jl_value_t*)li->specTypes; - fargs[3] = (jl_value_t*)def; + fargs[2] = (jl_value_t*)toplevel; #ifdef TRACE_INFERENCE jl_printf(JL_STDERR,"inference on "); jl_static_show_func_sig(JL_STDERR, (jl_value_t*)argtypes); jl_printf(JL_STDERR, "\n"); #endif -#ifdef ENABLE_INFERENCE - jl_value_t *newast = jl_apply(fargs, 4); - jl_value_t *defast = def->ast; - li->ast = jl_fieldref(newast, 0); - jl_gc_wb(li, li->ast); - li->rettype = jl_fieldref(newast, 1); - jl_gc_wb(li, li->rettype); - // if type inference bails out it returns def->ast - li->inferred = li->ast != defast; -#endif - li->inInference = 0; + jl_value_t *info = jl_apply(fargs, 3); (void)info; + assert(toplevel == jl_false || li->inInference == 0); + JL_UNLOCK(codegen); } - jl_in_inference = last_ii; - JL_UNLOCK(codegen); +#endif } jl_value_t *jl_nth_slot_type(jl_tupletype_t *sig, size_t i) @@ -828,7 +813,7 @@ static jl_lambda_info_t *cache_method(jl_methtable_t *mt, jl_tupletype_t *type, jl_gc_wb(method, method->specializations); if (jl_options.compile_enabled != JL_OPTIONS_COMPILE_OFF) // don't bother with typeinf if compile is off if (jl_symbol_name(newmeth->name)[0] != '@') // don't bother with typeinf on macros - jl_type_infer(newmeth, method); + jl_type_infer(newmeth, jl_false); } JL_GC_POP(); JL_UNLOCK(codegen); @@ -891,7 +876,7 @@ JL_DLLEXPORT jl_lambda_info_t *jl_instantiate_staged(jl_lambda_info_t *generator assert(jl_svec_len(generator->sparam_syms) == jl_svec_len(sparam_vals)); assert(generator->unspecialized == NULL && generator->specTypes == jl_anytuple_type); //if (!generated->inferred) - // jl_type_infer(generator, generator); // this doesn't help all that much + // jl_type_infer(generator, jl_false); // this doesn't help all that much ex = jl_exprn(lambda_sym, 2); @@ -1662,7 +1647,7 @@ static void _compile_all_deq(jl_array_t *found) linfo = jl_get_unspecialized(linfo); if (!linfo->inferred) { // force this function to be recompiled - jl_type_infer(linfo, linfo->def); + jl_type_infer(linfo, jl_false); linfo->functionObjects.functionObject = NULL; linfo->functionObjects.specFunctionObject = NULL; linfo->functionObjects.cFunctionList = NULL; @@ -1830,43 +1815,39 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) */ jl_lambda_info_t *mfunc = jl_method_table_assoc_exact(mt, args, nargs); - if (mfunc != NULL) { -#ifdef JL_TRACE - if (traceen) - jl_printf(JL_STDOUT, " at %s:%d\n", jl_symbol_name(mfunc->file), mfunc->line); -#endif - if (mfunc->inInference || mfunc->inCompile) { - // if inference is running on this function, return a copy - // of the function to be compiled without inference and run. - return verify_type(jl_call_unspecialized(mfunc->sparam_vals, jl_get_unspecialized(mfunc), args, nargs)); - } - assert(!mfunc->inInference); - return verify_type(jl_call_method_internal(mfunc, args, nargs)); - } - - // cache miss case - jl_tupletype_t *tt = arg_type_tuple(args, nargs); - // if running inference overwrites this particular method, it becomes - // unreachable from the method table, so root mfunc. - JL_GC_PUSH2(&tt, &mfunc); - mfunc = jl_mt_assoc_by_type(mt, tt, 1, 0); - + jl_tupletype_t *tt = NULL; if (mfunc == NULL) { + // cache miss case + tt = arg_type_tuple(args, nargs); + // if running inference overwrites this particular method, it becomes + // unreachable from the method table, so root mfunc. + JL_GC_PUSH2(&tt, &mfunc); + mfunc = jl_mt_assoc_by_type(mt, tt, 1, 0); + + if (mfunc == NULL) { #ifdef JL_TRACE - if (error_en) - show_call(F, args, nargs); + if (error_en) + show_call(F, args, nargs); #endif - JL_GC_POP(); - jl_no_method_error((jl_function_t*)F, args, nargs); - // unreachable + JL_GC_POP(); + jl_no_method_error((jl_function_t*)F, args, nargs); + // unreachable + } } #ifdef JL_TRACE if (traceen) jl_printf(JL_STDOUT, " at %s:%d\n", jl_symbol_name(mfunc->file), mfunc->line); #endif - assert(!mfunc->inInference); - jl_value_t *res = jl_call_method_internal(mfunc, args, nargs); - JL_GC_POP(); + jl_value_t *res; + if (mfunc->inInference || mfunc->inCompile) { + // if inference is running on this function, return a copy + // of the function to be compiled without inference and run. + res = jl_call_unspecialized(mfunc->sparam_vals, jl_get_unspecialized(mfunc), args, nargs); + } + else { + res = jl_call_method_internal(mfunc, args, nargs); + } + if (tt) JL_GC_POP(); return verify_type(res); } diff --git a/src/jltypes.c b/src/jltypes.c index 6a06343380248..b80fb15022a60 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3480,26 +3480,26 @@ void jl_init_types(void) jl_lambda_info_type = jl_new_datatype(jl_symbol("LambdaInfo"), jl_any_type, jl_emptysvec, - jl_svec(16, jl_symbol("ast"), jl_symbol("rettype"), + jl_svec(17, jl_symbol("ast"), jl_symbol("rettype"), jl_symbol("sparam_syms"), jl_symbol("sparam_vals"), jl_symbol("tfunc"), jl_symbol("name"), jl_symbol("roots"), - /* jl_symbol("specTypes"), - jl_symbol("unspecialized"), - jl_symbol("specializations")*/ - jl_symbol(""), jl_symbol(""), jl_symbol(""), + jl_symbol("specTypes"), + jl_symbol("unspecialized"), + jl_symbol("specializations"), jl_symbol("module"), jl_symbol("def"), jl_symbol("file"), jl_symbol("line"), jl_symbol("inferred"), - jl_symbol("pure")), - jl_svec(16, jl_any_type, jl_any_type, + jl_symbol("pure"), + jl_symbol("inInference")), + jl_svec(17, jl_any_type, jl_any_type, jl_simplevector_type, jl_simplevector_type, jl_any_type, jl_sym_type, jl_any_type, jl_any_type, jl_any_type, jl_array_any_type, jl_module_type, jl_any_type, jl_sym_type, jl_int32_type, - jl_bool_type, jl_bool_type), + jl_bool_type, jl_bool_type, jl_bool_type), 0, 1, 5); jl_typector_type = diff --git a/src/julia.h b/src/julia.h index cbb8bd73258f3..78fac3e311226 100644 --- a/src/julia.h +++ b/src/julia.h @@ -194,12 +194,11 @@ typedef struct _jl_lambda_info_t { int32_t line; int8_t inferred; int8_t pure; + int8_t inInference; // flags to tell if inference is running on this function uint8_t called; // bit flags: whether each of the first 8 arguments is called // hidden fields: uint8_t jlcall_api : 1; // the c-abi for fptr; 0 = jl_fptr_t, 1 = jl_fptr_sparam_t - uint8_t inInference : 1; // flags to tell if inference is running on this function - // used to avoid infinite recursion uint8_t inCompile : 1; uint8_t needs_sparam_vals_ducttape : 1; // if there are intrinsic calls, // probably require the sparams to compile successfully diff --git a/src/julia_internal.h b/src/julia_internal.h index 118fefc1958ce..6b2cff234ba3a 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -189,7 +189,7 @@ jl_value_t *jl_interpret_toplevel_expr(jl_value_t *e); jl_value_t *jl_static_eval(jl_value_t *ex, void *ctx_, jl_module_t *mod, jl_lambda_info_t *li, int sparams, int allow_alloc); int jl_is_toplevel_only_expr(jl_value_t *e); -void jl_type_infer(jl_lambda_info_t *li, jl_lambda_info_t *def); +void jl_type_infer(jl_lambda_info_t *li, jl_value_t *toplevel); jl_lambda_info_t *jl_get_unspecialized(jl_lambda_info_t *method); jl_lambda_info_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_t *types, diff --git a/src/toplevel.c b/src/toplevel.c index b45696874a015..1eb3fe14742ac 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -317,8 +317,6 @@ static int jl_eval_with_compiler_p(jl_lambda_info_t *li, jl_expr_t *expr, int co return 0; } -extern int jl_in_inference; - static jl_value_t *require_func=NULL; static jl_module_t *eval_import_path_(jl_array_t *args, int retrying) @@ -549,9 +547,7 @@ jl_value_t *jl_toplevel_eval_flex(jl_value_t *e, int fast) thk->specTypes = (jl_tupletype_t*)jl_typeof(jl_emptytuple); // no gc_wb needed if (ewc) { - if (!jl_in_inference) { - jl_type_infer(thk, thk); - } + jl_type_infer(thk, jl_true); jl_value_t *dummy_f_arg=NULL; result = jl_call_method_internal(thk, &dummy_f_arg, 1); } diff --git a/test/choosetests.jl b/test/choosetests.jl index 1e8f35743beda..9ef34df4f2152 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -15,7 +15,7 @@ Upon return, `tests` is a vector of fully-expanded test names, and """ -> function choosetests(choices = []) testnames = [ - "linalg", "core", "keywordargs", "numbers", "printf", + "linalg", "core", "inference", "keywordargs", "numbers", "printf", "char", "string", "triplequote", "unicode", "dates", "dict", "hashing", "remote", "iobuffer", "staged", "arrayops", "tuple", "subarray", "reduce", "reducedim", "random", diff --git a/test/inference.jl b/test/inference.jl new file mode 100644 index 0000000000000..12faab36fc1f9 --- /dev/null +++ b/test/inference.jl @@ -0,0 +1,24 @@ +# This file is a part of Julia. License is MIT: http://julialang.org/license + +# test for Core.Inference correctness and precision + +const Bottom = Union{} + +# issue 9770 +@noinline x9770() = false +function f9770(x) + if x9770() + g9770(:a, :foo) + else + x + end +end +function g9770(x,y) + if isa(y, Symbol) + f9770(x) + else + g9770(:a, :foo) + end +end +@test g9770(:a, "c") === :a +@test g9770(:b, :c) === :b diff --git a/test/staged.jl b/test/staged.jl index 7e099d3ef7474..8cc083da0ed95 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -147,7 +147,7 @@ module TestGeneratedThrow foo() = (bar(rand() > 0.5 ? 1 : 1.0); error("foo")) function __init__() code_typed(foo,(); optimize = false) - @test isa(Core.Inference.inference_stack,Core.Inference.EmptyCallStack) + @test Core.Inference.isempty(Core.Inference.active) && Core.Inference.isempty(Core.Inference.workq) cfunction(foo,Void,()) end end From f67203c6d4f0c55c65b39bdc5f159b3bbee1002c Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 1 Mar 2016 13:27:29 -0500 Subject: [PATCH 2/7] memoize type_depth computation --- base/inference.jl | 20 ++++++++++++-------- src/alloc.c | 1 + src/jltypes.c | 12 +++++++----- src/julia.h | 1 + 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 8fd09d66952a9..f408f5d8152de 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -346,16 +346,20 @@ add_tfunc(typeof, 1, 1, typeof_tfunc) add_tfunc(typeassert, 2, 2, (A, v, t)->(isType(t) ? typeintersect(v,t.parameters[1]) : Any)) -function type_depth(t::ANY, d::Int=0) - if isa(t,Union) - t === Bottom && return d - return maximum(x->type_depth(x, d+1), t.types) - elseif isa(t,DataType) +function type_depth(t::ANY) + if isa(t, Union) + t === Bottom && return 0 + return maximum(type_depth, t.types) + 1 + elseif isa(t, DataType) + t = t::DataType P = t.parameters - isempty(P) && return d - return maximum(x->type_depth(x, d+1), P) + isempty(P) && return 0 + if t.depth == 0 + t.depth = maximum(type_depth, P) + 1 + end + return t.depth end - return d + return 0 end function limit_type_depth(t::ANY, d::Int, cov::Bool, vars) diff --git a/src/alloc.c b/src/alloc.c index 6b9323a5e9ecd..031e28083c7fb 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -596,6 +596,7 @@ JL_DLLEXPORT jl_datatype_t *jl_new_uninitialized_datatype(size_t nfields, int8_t t->nfields = nfields; t->haspadding = 0; t->pointerfree = 0; + t->depth = 0; return t; } diff --git a/src/jltypes.c b/src/jltypes.c index b80fb15022a60..95460b29ebea3 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3189,7 +3189,7 @@ extern void jl_init_int32_int64_cache(void); void jl_init_types(void) { // create base objects - jl_datatype_type = jl_new_uninitialized_datatype(10, 1); + jl_datatype_type = jl_new_uninitialized_datatype(11, 1); jl_set_typeof(jl_datatype_type, jl_datatype_type); jl_typename_type = jl_new_uninitialized_datatype(8, 1); jl_sym_type = jl_new_uninitialized_datatype(0, 1); @@ -3211,7 +3211,7 @@ void jl_init_types(void) jl_datatype_type->name->primary = (jl_value_t*)jl_datatype_type; jl_datatype_type->super = jl_type_type; jl_datatype_type->parameters = jl_emptysvec; - jl_datatype_type->name->names = jl_svec(10, jl_symbol("name"), + jl_datatype_type->name->names = jl_svec(11, jl_symbol("name"), jl_symbol("super"), jl_symbol("parameters"), jl_symbol("types"), @@ -3220,12 +3220,13 @@ void jl_init_types(void) jl_symbol("abstract"), jl_symbol("mutable"), jl_symbol("pointerfree"), - jl_symbol("ninitialized")); - jl_datatype_type->types = jl_svec(10, jl_typename_type, jl_type_type, + jl_symbol("ninitialized"), + jl_symbol("depth")); + jl_datatype_type->types = jl_svec(11, jl_typename_type, jl_type_type, jl_simplevector_type, jl_simplevector_type, jl_any_type, jl_any_type, // size - jl_any_type, jl_any_type, jl_any_type, jl_any_type); + jl_any_type, jl_any_type, jl_any_type, jl_any_type, jl_any_type); jl_datatype_type->instance = NULL; jl_datatype_type->uid = jl_assign_type_uid(); jl_datatype_type->struct_decl = NULL; @@ -3544,6 +3545,7 @@ void jl_init_types(void) jl_svecset(jl_datatype_type->types, 7, (jl_value_t*)jl_bool_type); jl_svecset(jl_datatype_type->types, 8, (jl_value_t*)jl_bool_type); jl_svecset(jl_datatype_type->types, 9, jl_int32_type); + jl_svecset(jl_datatype_type->types, 10, jl_int32_type); jl_svecset(jl_tvar_type->types, 3, (jl_value_t*)jl_bool_type); jl_svecset(jl_simplevector_type->types, 0, jl_long_type); jl_svecset(jl_typename_type->types, 6, jl_long_type); diff --git a/src/julia.h b/src/julia.h index 78fac3e311226..23e17c45d63ea 100644 --- a/src/julia.h +++ b/src/julia.h @@ -274,6 +274,7 @@ typedef struct _jl_datatype_t { uint8_t mutabl; uint8_t pointerfree; int32_t ninitialized; + int32_t depth; // hidden fields: uint32_t nfields; uint32_t alignment : 29; // strictest alignment over all fields From af8143127a9ac0adcd60bde1b9e370a31cd8f182 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 1 Mar 2016 13:31:22 -0500 Subject: [PATCH 3/7] hot-path optimize replace_getfield! --- base/inference.jl | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index f408f5d8152de..a03501376478e 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -3511,10 +3511,7 @@ function alloc_elim_pass(ast::Expr, sv::VarInfo) end end -function replace_getfield!(ast, e::ANY, tupname, vals, field_names, sv, i0) - if !isa(e,Expr) - return - end +function replace_getfield!(ast, e::Expr, tupname, vals, field_names, sv, i0) for i = i0:length(e.args) a = e.args[i] if isa(a,Expr) && is_known_call(a, getfield, sv) && @@ -3548,8 +3545,8 @@ function replace_getfield!(ast, e::ANY, tupname, vals, field_names, sv, i0) end end e.args[i] = val - else - replace_getfield!(ast, a, tupname, vals, field_names, sv, 1) + elseif isa(a, Expr) + replace_getfield!(ast, a::Expr, tupname, vals, field_names, sv, 1) end end end From 638211647dc17644f05caec4830921e220c31585 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 1 Mar 2016 17:45:48 -0500 Subject: [PATCH 4/7] implement recursive type-inference since in the common case, the call graph is a simple DAG, and it is faster / more efficient to infer immediately rather than building the edges graph and repeating method lookup process after the backedge finishes --- base/inference.jl | 411 ++++++++++++++++++++++++---------------------- 1 file changed, 212 insertions(+), 199 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index a03501376478e..61880011169f8 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1599,20 +1599,17 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee end if tfunc_idx == -1 - if caller === nothing && needtree && in_typeinf_loop + if caller === nothing && needtree && in_typeinf_loop && !linfo.inInference # if the caller needed the ast, but we are already in the typeinf loop # then just return early -- we can't fulfill this request - # if the client was typeinf_ext(toplevel), then we cancel the inInference request # if the client was inlining, then this means we decided not to try to infer this # particular signature (due to signature coarsening in abstract_call_gf_by_type) # and attempting to force it now would be a bad idea (non terminating) - linfo.inInference = false return (nothing, Union{}, false) end # add lam to be inferred and record the edge ast = ccall(:jl_prepare_ast, Any, (Any,), linfo)::Expr sv = VarInfo(linfo, atypes, ast) - if length(linfo.sparam_vals) > 0 # handled by VarInfo constructor elseif isempty(sparams) && !isempty(linfo.sparam_syms) @@ -1620,11 +1617,8 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee else sv.sp = sparams end - # our stack frame inference context frame = InferenceState(sv, linfo, optimize) - push!(workq, frame) - frame.inworkq = true if cached #println(linfo) @@ -1658,7 +1652,7 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee push!(frame.backedges, (me, Ws)) end end - typeinf_loop() + typeinf_loop(frame) return (frame.sv.ast, frame.bestguess, frame.inferred) end @@ -1676,14 +1670,15 @@ function typeinf_uncached(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, return typeinf_edge(linfo, atypes, sparams, true, optimize, false, nothing) end function typeinf_ext(linfo::LambdaInfo, toplevel::Bool) - return typeinf_edge(linfo, linfo.specTypes, svec(), toplevel, true, true, nothing) + return typeinf_edge(linfo, linfo.specTypes, svec(), true, true, true, nothing) end in_typeinf_loop = false -function typeinf_loop() +function typeinf_loop(frame) global in_typeinf_loop if in_typeinf_loop + frame.inworkq || typeinf_frame(frame) return end in_typeinf_loop = true @@ -1700,121 +1695,149 @@ function typeinf_loop() frame = pop!(workq) frame.inworkq = false end - global global_sv = frame.sv # TODO: actually pass this to all functions that need it - W = frame.ip - s = frame.stmt_types - n = frame.nstmts - while !isempty(W) - # make progress on the active ip set - local pc::Int = first(W), pc´::Int - while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 - #print(pc,": ",s[pc],"\n") - delete!(W, pc) - frame.sv.currpc = pc - frame.sv.static_typeof = false - if frame.handler_at[pc] === 0 - frame.handler_at[pc] = frame.cur_hand - else - frame.cur_hand = frame.handler_at[pc] - end - stmt = frame.sv.body[pc] - changes = abstract_interpret(stmt, s[pc]::ObjectIdDict, frame.sv) - if changes === () - # if there was a Expr(:static_typeof) on this line, - # need to continue to the next pc even though the return type was Bottom - # otherwise, this line threw an error and there is no need to continue - frame.sv.static_typeof || break - changes = s[pc] + typeinf_frame(frame) + if isempty(workq) && nactive[] > 0 + # nothing in active has an edge that hasn't reached a fixed-point + # so all of them can be considered finished now + for i in active + i === nothing && continue + i = i::InferenceState + i.fixedpoint && finish(i) + end + end + end + # cleanup the active queue + empty!(active) +# while active[end] === nothing +# # this pops everything, but with exaggerated care just in case +# # something managed to add something to the queue at the same time +# # (or someone decides to use an alternative termination condition) +# pop!(active) +# end + in_typeinf_loop = false + nothing +end + +global_sv = nothing +function typeinf_frame(frame) + global global_sv # TODO: actually pass this to all functions that need it + last_global_sv = global_sv + global_sv = frame.sv + W = frame.ip + s = frame.stmt_types + n = frame.nstmts + @label restart_typeinf + while !isempty(W) + # make progress on the active ip set + local pc::Int = first(W), pc´::Int + while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 + #print(pc,": ",s[pc],"\n") + delete!(W, pc) + frame.sv.currpc = pc + frame.sv.static_typeof = false + if frame.handler_at[pc] === 0 + frame.handler_at[pc] = frame.cur_hand + else + frame.cur_hand = frame.handler_at[pc] + end + stmt = frame.sv.body[pc] + changes = abstract_interpret(stmt, s[pc]::ObjectIdDict, frame.sv) + if changes === () + # if there was a Expr(:static_typeof) on this line, + # need to continue to the next pc even though the return type was Bottom + # otherwise, this line threw an error and there is no need to continue + frame.sv.static_typeof || break + changes = s[pc] + end + if frame.cur_hand !== () + # propagate type info to exception handler + l = frame.cur_hand[1] + if stchanged(changes, s[l], frame.sv.vars) + push!(W, l) + s[l] = stupdate(s[l], changes, frame.sv.vars) end - if frame.cur_hand !== () - # propagate type info to exception handler - l = frame.cur_hand[1] - if stchanged(changes, s[l], frame.sv.vars) - push!(W, l) - s[l] = stupdate(s[l], changes, frame.sv.vars) + end + pc´ = pc+1 + if isa(changes, StateUpdate) && isa((changes::StateUpdate).var, GenSym) + # directly forward changes to a GenSym to the applicable line + changes = changes::StateUpdate + id = (changes.var::GenSym).id + 1 + new = changes.vtype.typ + old = frame.sv.gensym_types[id] + if old===NF || !(new <: old) + frame.sv.gensym_types[id] = tmerge(old, new) + for r in frame.gensym_uses[id] + if !is(s[r], ()) # s[r] === () => unreached statement + push!(W, r) + end end end - pc´ = pc+1 - if isa(changes, StateUpdate) && isa((changes::StateUpdate).var, GenSym) - # directly forward changes to a GenSym to the applicable line - changes = changes::StateUpdate - id = (changes.var::GenSym).id + 1 - new = changes.vtype.typ - old = frame.sv.gensym_types[id] - if old===NF || !(new <: old) - frame.sv.gensym_types[id] = tmerge(old, new) - for r in frame.gensym_uses[id] - if !is(s[r], ()) # s[r] === () => unreached statement - push!(W, r) - end + elseif isa(stmt, GotoNode) + pc´ = findlabel(frame.labels, (stmt::GotoNode).label) + elseif isa(stmt, Expr) + stmt = stmt::Expr + hd = stmt.head + if is(hd, :gotoifnot) + condexpr = stmt.args[1] + l = findlabel(frame.labels, stmt.args[2]::Int) + # constant conditions + if is(condexpr, true) + elseif is(condexpr, false) + pc´ = l + else + # general case + frame.handler_at[l] = frame.cur_hand + if stchanged(changes, s[l], frame.sv.vars) + # add else branch to active IP list + push!(W, l) + s[l] = stupdate(s[l], changes, frame.sv.vars) end end - elseif isa(stmt, GotoNode) - pc´ = findlabel(frame.labels, (stmt::GotoNode).label) - elseif isa(stmt, Expr) - stmt = stmt::Expr - hd = stmt.head - if is(hd, :gotoifnot) - condexpr = stmt.args[1] - l = findlabel(frame.labels, stmt.args[2]::Int) - # constant conditions - if is(condexpr, true) - elseif is(condexpr, false) - pc´ = l - else - # general case - frame.handler_at[l] = frame.cur_hand - if stchanged(changes, s[l], frame.sv.vars) - # add else branch to active IP list - push!(W, l) - s[l] = stupdate(s[l], changes, frame.sv.vars) - end - end - elseif is(hd, :type_goto) - for i = 2:length(stmt.args) - var = stmt.args[i]::GenSym - # Store types that need to be fed back via type_goto - # in gensym_init. After finishing inference, if any - # of these types changed, start over with the fed-back - # types known from the beginning. - # See issue #3821 (using !typeseq instead of !subtype), - # and issue #7810. - id = var.id+1 - vt = frame.sv.gensym_types[id] - ot = frame.gensym_init[id] - if ot===NF || !typeseq(vt, ot) - frame.gensym_init[id] = vt - if get(frame.sv.fedbackvars, var, false) - frame.typegotoredo = true - end + elseif is(hd, :type_goto) + for i = 2:length(stmt.args) + var = stmt.args[i]::GenSym + # Store types that need to be fed back via type_goto + # in gensym_init. After finishing inference, if any + # of these types changed, start over with the fed-back + # types known from the beginning. + # See issue #3821 (using !typeseq instead of !subtype), + # and issue #7810. + id = var.id+1 + vt = frame.sv.gensym_types[id] + ot = frame.gensym_init[id] + if ot===NF || !typeseq(vt, ot) + frame.gensym_init[id] = vt + if get(frame.sv.fedbackvars, var, false) + frame.typegotoredo = true end - frame.sv.fedbackvars[var] = true end - elseif is(hd, :return) - pc´ = n + 1 - rt = abstract_eval(stmt.args[1], s[pc], frame.sv) - if tchanged(rt, frame.bestguess) - # new (wider) return type for frame - frame.bestguess = tmerge(frame.bestguess, rt) - for (caller, callerW) in frame.backedges - # notify backedges of updated type information - for caller_pc in callerW - push!(caller.ip, caller_pc) - end + frame.sv.fedbackvars[var] = true + end + elseif is(hd, :return) + pc´ = n + 1 + rt = abstract_eval(stmt.args[1], s[pc], frame.sv) + if tchanged(rt, frame.bestguess) + # new (wider) return type for frame + frame.bestguess = tmerge(frame.bestguess, rt) + for (caller, callerW) in frame.backedges + # notify backedges of updated type information + for caller_pc in callerW + push!(caller.ip, caller_pc) end - unmark_fixedpoint(frame) - end - elseif is(hd, :enter) - l = findlabel(frame.labels, stmt.args[1]::Int) - frame.cur_hand = (l, frame.cur_hand) - # propagate type info to exception handler - l = frame.cur_hand[1] - old = s[l] - new = s[pc]::ObjectIdDict - if old === () || stchanged(new, old::ObjectIdDict, frame.sv.vars) - push!(W, l) - s[l] = stupdate(old, new, frame.sv.vars) end + unmark_fixedpoint(frame) + end + elseif is(hd, :enter) + l = findlabel(frame.labels, stmt.args[1]::Int) + frame.cur_hand = (l, frame.cur_hand) + # propagate type info to exception handler + l = frame.cur_hand[1] + old = s[l] + new = s[pc]::ObjectIdDict + if old === () || stchanged(new, old::ObjectIdDict, frame.sv.vars) + push!(W, l) + s[l] = stupdate(old, new, frame.sv.vars) + end # if frame.handler_at[l] === 0 # frame.n_handlers += 1 # if frame.n_handlers > 25 @@ -1825,103 +1848,88 @@ function typeinf_loop() # return (ast0, Any, false) # end # end - frame.handler_at[l] = frame.cur_hand - elseif is(hd, :leave) - for i = 1:((stmt.args[1])::Int) - frame.cur_hand = frame.cur_hand[2] - end + frame.handler_at[l] = frame.cur_hand + elseif is(hd, :leave) + for i = 1:((stmt.args[1])::Int) + frame.cur_hand = frame.cur_hand[2] end end - if pc´<=n && (frame.handler_at[pc´] = frame.cur_hand; true) && - stchanged(changes, s[pc´], frame.sv.vars) - s[pc´] = stupdate(s[pc´], changes, frame.sv.vars) - pc = pc´ - elseif pc´ in W - pc = pc´ - else - break - end end - end - - # with no active ip's, type inference on frame is done if there are no outstanding (unfinished) edges - finished = isempty(frame.edges) - if isempty(workq) - # oops, there's a cycle somewhere in the `edges` graph - # so we've run out off the tree and will need to start work on the loop - frame.fixedpoint = true - end - - restart = false - if finished || frame.fixedpoint - if frame.typegotoredo - # if any type_gotos changed, clear state and restart. - frame.typegotoredo = false - for ll = 2:length(s) - s[ll] = () - end - empty!(W) - push!(W, 1) - frame.cur_hand = () - frame.handler_at = Any[ () for i=1:n ] - frame.n_handlers = 0 - frame.sv.gensym_types[:] = frame.gensym_init - restart = true + if pc´<=n && (frame.handler_at[pc´] = frame.cur_hand; true) && + stchanged(changes, s[pc´], frame.sv.vars) + s[pc´] = stupdate(s[pc´], changes, frame.sv.vars) + pc = pc´ + elseif pc´ in W + pc = pc´ else - # if a static_typeof was never reached, - # use Union{} as its real type and continue - # running type inference from its uses - # (one of which is the static_typeof) - # TODO: this restart should happen just before calling finish() - for (fbvar, seen) in frame.sv.fedbackvars - if !seen - frame.sv.fedbackvars[fbvar] = true - id = (fbvar::GenSym).id + 1 - for r in frame.gensym_uses[id] - if !is(s[r], ()) # s[r] === () => unreached statement - push!(W, r) - end - end - restart = true - end - end + break end + end + end - if restart - push!(workq, frame) - frame.inworkq = true - elseif finished - finish(frame) - else # fixedpoint propagation - for (i,_) in frame.edges - i = i::InferenceState - if !i.fixedpoint - i.inworkq || push!(workq, i) - i.inworkq = true - i.fixedpoint = true + if frame.inferred + # during recursive compilation, this can happen. + # now just need to bail since it's already finished. + last_global_sv = global_sv + return + end + + # with no active ip's, type inference on frame is done if there are no outstanding (unfinished) edges + finished = isempty(frame.edges) + if isempty(workq) + # oops, there's a cycle somewhere in the `edges` graph + # so we've run out off the tree and will need to start work on the loop + frame.fixedpoint = true + end + + if finished || frame.fixedpoint + if frame.typegotoredo + # if any type_gotos changed, clear state and restart. + frame.typegotoredo = false + for ll = 2:length(s) + s[ll] = () + end + empty!(W) + push!(W, 1) + frame.cur_hand = () + frame.handler_at = Any[ () for i=1:n ] + frame.n_handlers = 0 + frame.sv.gensym_types[:] = frame.gensym_init + @goto restart_typeinf + else + # if a static_typeof was never reached, + # use Union{} as its real type and continue + # running type inference from its uses + # (one of which is the static_typeof) + # TODO: this restart should happen just before calling finish() + for (fbvar, seen) in frame.sv.fedbackvars + if !seen + frame.sv.fedbackvars[fbvar] = true + id = (fbvar::GenSym).id + 1 + for r in frame.gensym_uses[id] + if !is(s[r], ()) # s[r] === () => unreached statement + push!(W, r) + end end + @goto restart_typeinf end end end - if !restart && isempty(workq) && nactive[] > 0 - # nothing in active has an edge that hasn't reached a fixed-point - # so all of them can be considered finished now - for i in active - i === nothing && continue + + if finished + finish(frame) + else # fixedpoint propagation + for (i,_) in frame.edges i = i::InferenceState - i.fixedpoint && finish(i) + if !i.fixedpoint + i.inworkq || push!(workq, i) + i.inworkq = true + i.fixedpoint = true + end end end end - # cleanup the active queue - empty!(active) -# while active[end] === nothing -# # this pops everything, but with exaggerated care just in case -# # something managed to add something to the queue at the same time -# # (or someone decides to use an alternative termination condition) -# pop!(active) -# end - in_typeinf_loop = false + global_sv = last_global_sv nothing end @@ -1950,6 +1958,11 @@ function finish(me::InferenceState) for (i,_) in me.edges @assert (i::InferenceState).fixedpoint end + # below may call back into inference and + # see this InferenceState is in an imcomplete state + # set `inworkq` to prevent it from trying to look + # at the object in any detail + me.inworkq = true # annotate fulltree with type information for i = 1:length(me.sv.gensym_types) From 24276a5226a33ce4af21cb3448c835713bab2c52 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 2 Mar 2016 01:52:06 -0500 Subject: [PATCH 5/7] merge VarInfo and InferenceState types --- base/inference.jl | 226 +++++++++++++++++++++------------------------- 1 file changed, 104 insertions(+), 122 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 61880011169f8..7d34abe1970b6 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -14,9 +14,12 @@ typealias LineNum Int typealias VarTable ObjectIdDict -#TODO: the distinction between VarInfo and InferenceState is arbitrary and unnecessary +type VarState + typ + undef::Bool +end -type VarInfo +type InferenceState atypes #::Type # type sig ast #::Expr body::Array{Any,1} # ast body @@ -29,36 +32,8 @@ type VarInfo mod::Module currpc::LineNum static_typeof::Bool - inf #::InferenceState - - function VarInfo(linfo::LambdaInfo, sig::ANY=linfo.specTypes, ast=linfo.ast) - if !isa(ast,Expr) - ast = ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, ast) - end - assert(is(ast.head,:lambda)) - vinflist = ast.args[2][1]::Array{Any,1} - vars = map(vi->vi[1], vinflist) - body = (ast.args[3].args)::Array{Any,1} - ngs = ast.args[2][3] - if !isa(ngs,Int) - ngs = length(ngs::Array) - end - gensym_types = Any[ NF for i = 1:(ngs::Int) ] - nl = label_counter(body)+1 - sp = linfo.sparam_vals - - return new(sig, ast, body, sp, vars, gensym_types, vinflist, nl, Dict{GenSym, Bool}(), linfo.module, 0, false) - end -end - -type VarState - typ - undef::Bool -end -type InferenceState # info on the state of inference and the linfo - sv::VarInfo linfo::LambdaInfo args::Vector{Any} labels::Vector{Int} @@ -87,9 +62,26 @@ type InferenceState optimize::Bool inferred::Bool tfunc_idx::Int - function InferenceState(sv::VarInfo, linfo::LambdaInfo, optimize::Bool) - body = sv.body - labels = zeros(Int, sv.label_counter) + + function InferenceState(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, ast, optimize::Bool) + if !isa(ast,Expr) + ast = ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, ast) + end + assert(is(ast.head,:lambda)) + vinflist = ast.args[2][1]::Array{Any,1} + vars = map(vi->vi[1], vinflist) + body = (ast.args[3].args)::Array{Any,1} + nl = label_counter(body)+1 + + if length(linfo.sparam_vals) > 0 + sp = linfo.sparam_vals + elseif isempty(sparams) && !isempty(linfo.sparam_syms) + sp = svec(Any[ TypeVar(sym, Any, true) for sym in linfo.sparam_syms ]...) + else + sp = sparams + end + + labels = zeros(Int, nl) for i = 1:length(body) b = body[i] if isa(b,LabelNode) @@ -101,15 +93,14 @@ type InferenceState s = Any[ () for i=1:n ] # initial types s[1] = ObjectIdDict() - for v in sv.vars + for v in vars s[1][v] = VarState(Bottom,true) end - args = f_argnames(sv.ast) + args = f_argnames(ast) la = length(args) - atypes = sv.atypes if la > 0 - lastarg = sv.ast.args[1][la] + lastarg = ast.args[1][la] if is_rest_arg(lastarg) if atypes === Tuple if la > 1 @@ -151,10 +142,8 @@ type InferenceState end gensym_uses = find_gensym_uses(body) - if length(sv.gensym_types) != length(gensym_uses) - sv.gensym_types = Any[ NF for i=1:length(gensym_uses) ] - end - gensym_init = copy(sv.gensym_types) + gensym_types = Any[ NF for i=1:length(gensym_uses) ] + gensym_init = copy(gensym_types) # exception handlers cur_hand = () @@ -164,7 +153,10 @@ type InferenceState W = IntSet() push!(W, 1) #initial pc to visit - frame = new(sv, linfo, args, labels, s, Union{}, W, n, + frame = new( + atypes, ast, body, sp, vars, gensym_types, vinflist, nl, Dict{GenSym, Bool}(), linfo.module, 0, false, + + linfo, args, labels, s, Union{}, W, n, cur_hand, handler_at, n_handlers, gensym_uses, gensym_init, ObjectIdDict(), #Dict{InferenceState, Vector{LineNum}}(), @@ -172,7 +164,6 @@ type InferenceState false, false, false, optimize, false, -1) push!(active, frame) nactive[] += 1 - sv.inf = frame return frame end end @@ -194,8 +185,8 @@ function _any(f::ANY, a) return false end -function is_static_parameter(sv::VarInfo, s::Symbol) - sp = sv.inf.linfo.sparam_syms +function is_static_parameter(sv::InferenceState, s::Symbol) + sp = sv.linfo.sparam_syms for i=1:length(sp) if is(sp[i],s) return true @@ -213,9 +204,9 @@ function contains_is(itr, x::ANY) return false end -is_local(sv::VarInfo, s::GenSym) = true -is_local(sv::VarInfo, s::Symbol) = contains_is(sv.vars, s) -is_global(sv::VarInfo, s::Symbol) = !is_local(sv,s) && !is_static_parameter(sv,s) +is_local(sv::InferenceState, s::GenSym) = true +is_local(sv::InferenceState, s::Symbol) = contains_is(sv.vars, s) +is_global(sv::InferenceState, s::Symbol) = !is_local(sv,s) && !is_static_parameter(sv,s) function _iisconst(s::Symbol, sv) m = sv.mod @@ -226,7 +217,7 @@ _ieval(x::ANY, sv) = ccall(:jl_interpret_toplevel_expr_in, Any, (Any, Any, Any, Any), sv.mod, x, svec(), svec()) -_topmod(sv::VarInfo) = _topmod(sv.mod) +_topmod(sv::InferenceState) = _topmod(sv.mod) _topmod(m::Module) = ccall(:jl_base_relative_to, Any, (Any,), m)::Module function istopfunction(topmod, f::ANY, sym) @@ -563,12 +554,12 @@ const apply_type_tfunc = function (A::ANY, args...) push!(tparams, aip1) else if i<=lA - val = extract_simple_tparam(A[i], global_sv::VarInfo) + val = extract_simple_tparam(A[i], global_sv::InferenceState) if val !== Bottom push!(tparams, val) continue elseif isa(A[i],Symbol) - sp = global_sv.inf.linfo.sparam_syms + sp = global_sv.linfo.sparam_syms s = A[i] found = false for j=1:length(sp) @@ -687,7 +678,7 @@ function builtin_tfunction(f::ANY, args::ANY, argtype::ANY) return tf[3](argtypes...) end -function isconstantref(f::ANY, sv::VarInfo) +function isconstantref(f::ANY, sv::InferenceState) if isa(f,TopNode) m = _topmod(sv) return isconst(m, f.name) && isdefined(m, f.name) && f @@ -864,9 +855,9 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e, sv) ls = length(sig.parameters) # look at the existing edges to detect growing argument lists limitlength = false - for (callee, _) in (sv.inf::InferenceState).edges + for (callee, _) in sv.edges callee = callee::InferenceState - if linfo.def === callee.linfo.def && ls > length(callee.sv.atypes.parameters) + if linfo.def === callee.linfo.def && ls > length(callee.atypes.parameters) limitlength = true break end @@ -879,16 +870,16 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e, sv) infstate = infstate::InferenceState if linfo.def === infstate.linfo.def td = type_depth(sig) - if ls > length(infstate.sv.atypes.parameters) + if ls > length(infstate.atypes.parameters) limitlength = true end - if td > type_depth(infstate.sv.atypes) + if td > type_depth(infstate.atypes) # impose limit if we recur and the argument types grow beyond MAX_TYPE_DEPTH if td > MAX_TYPE_DEPTH sig = limit_type_depth(sig, 0, true, []) break else - p1, p2 = sig.parameters, infstate.sv.atypes.parameters + p1, p2 = sig.parameters, infstate.atypes.parameters if length(p2) == ls limitdepth = false newsig = Array(Any, ls) @@ -953,7 +944,7 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, e, sv) return rettype end -function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::VarInfo) +function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::InferenceState) argtype = typeintersect(types,limit_tuple_type(argtype)) if is(argtype,Bottom) return Bottom @@ -1035,7 +1026,7 @@ function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable return abstract_call(af, (), Any[type_typeof(af), Vararg{Any}], vtypes, sv, ()) end -function isconstantargs(args, argtypes::Vector{Any}, sv::VarInfo) +function isconstantargs(args, argtypes::Vector{Any}, sv::InferenceState) if length(argtypes) == 1 # just the function return true end @@ -1055,7 +1046,7 @@ function isconstantargs(args, argtypes::Vector{Any}, sv::VarInfo) return true end -function _ieval_args(args, argtypes::Vector{Any}, sv::VarInfo) +function _ieval_args(args, argtypes::Vector{Any}, sv::InferenceState) c = cell(length(argtypes) - 1) for i = 2:length(argtypes) t = argtypes[i] @@ -1108,7 +1099,7 @@ function pure_eval_call(f::ANY, fargs, argtypes::ANY, sv, e) end -function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, sv::VarInfo, e) +function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, e) t = pure_eval_call(f, fargs, argtypes, sv, e) t !== false && return t if is(f,_apply) && length(fargs)>1 @@ -1166,7 +1157,7 @@ function abstract_call(f::ANY, fargs, argtypes::Vector{Any}, vtypes::VarTable, s return abstract_call_gf(f, fargs, Tuple{argtypes...}, e, sv) end -function abstract_eval_call(e, vtypes::VarTable, sv::VarInfo) +function abstract_eval_call(e, vtypes::VarTable, sv::InferenceState) argtypes = Any[abstract_eval(a, vtypes, sv) for a in e.args] #print("call ", e.args[1], argtypes, "\n\n") for x in argtypes @@ -1203,7 +1194,7 @@ function abstract_eval_call(e, vtypes::VarTable, sv::VarInfo) return abstract_call(f, e.args, argtypes, vtypes, sv, e) end -function abstract_eval(e::ANY, vtypes::VarTable, sv::VarInfo) +function abstract_eval(e::ANY, vtypes::VarTable, sv::InferenceState) if isa(e,QuoteNode) v = (e::QuoteNode).value return type_typeof(v) @@ -1315,7 +1306,7 @@ function abstract_eval_global(M::Module, s::Symbol) return Any end -function abstract_eval_gensym(s::GenSym, sv::VarInfo) +function abstract_eval_gensym(s::GenSym, sv::InferenceState) typ = sv.gensym_types[s.id+1] if typ === NF return Bottom @@ -1323,10 +1314,10 @@ function abstract_eval_gensym(s::GenSym, sv::VarInfo) return typ end -function abstract_eval_symbol(s::Symbol, vtypes::ObjectIdDict, sv::VarInfo) +function abstract_eval_symbol(s::Symbol, vtypes::ObjectIdDict, sv::InferenceState) t = get(vtypes,s,NF) if is(t,NF) - sp = sv.inf.linfo.sparam_syms + sp = sv.linfo.sparam_syms for i=1:length(sp) if is(sp[i],s) # static parameter @@ -1370,7 +1361,7 @@ function getindex(x::StateUpdate, s::Symbol) return get(x.state,s,NF) end -function abstract_interpret(e::ANY, vtypes::VarTable, sv::VarInfo) +function abstract_interpret(e::ANY, vtypes::VarTable, sv::InferenceState) !isa(e,Expr) && return vtypes # handle assignment if is(e.head,:(=)) @@ -1538,7 +1529,7 @@ function find_gensym_uses(e::ANY, uses, line) end end -function newvar!(sv::VarInfo, typ) +function newvar!(sv::InferenceState, typ) id = length(sv.gensym_types) push!(sv.gensym_types, typ) return GenSym(id) @@ -1609,16 +1600,8 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee end # add lam to be inferred and record the edge ast = ccall(:jl_prepare_ast, Any, (Any,), linfo)::Expr - sv = VarInfo(linfo, atypes, ast) - if length(linfo.sparam_vals) > 0 - # handled by VarInfo constructor - elseif isempty(sparams) && !isempty(linfo.sparam_syms) - sv.sp = svec(Any[ TypeVar(sym, Any, true) for sym in linfo.sparam_syms ]...) - else - sv.sp = sparams - end # our stack frame inference context - frame = InferenceState(sv, linfo, optimize) + frame = InferenceState(linfo, atypes, sparams, ast, optimize) if cached #println(linfo) @@ -1639,21 +1622,20 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee end if caller !== nothing - me = caller.inf - if haskey(me.edges, frame) - Ws = me.edges[frame]::Vector{Int} + if haskey(caller.edges, frame) + Ws = caller.edges[frame]::Vector{Int} if !(caller.currpc in Ws) push!(Ws, caller.currpc) end else @assert caller.currpc > 0 Ws = Int[caller.currpc] - me.edges[frame] = Ws - push!(frame.backedges, (me, Ws)) + caller.edges[frame] = Ws + push!(frame.backedges, (caller, Ws)) end end typeinf_loop(frame) - return (frame.sv.ast, frame.bestguess, frame.inferred) + return (frame.ast, frame.bestguess, frame.inferred) end function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, caller) @@ -1722,7 +1704,7 @@ global_sv = nothing function typeinf_frame(frame) global global_sv # TODO: actually pass this to all functions that need it last_global_sv = global_sv - global_sv = frame.sv + global_sv = frame W = frame.ip s = frame.stmt_types n = frame.nstmts @@ -1733,28 +1715,28 @@ function typeinf_frame(frame) while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 #print(pc,": ",s[pc],"\n") delete!(W, pc) - frame.sv.currpc = pc - frame.sv.static_typeof = false + frame.currpc = pc + frame.static_typeof = false if frame.handler_at[pc] === 0 frame.handler_at[pc] = frame.cur_hand else frame.cur_hand = frame.handler_at[pc] end - stmt = frame.sv.body[pc] - changes = abstract_interpret(stmt, s[pc]::ObjectIdDict, frame.sv) + stmt = frame.body[pc] + changes = abstract_interpret(stmt, s[pc]::ObjectIdDict, frame) if changes === () # if there was a Expr(:static_typeof) on this line, # need to continue to the next pc even though the return type was Bottom # otherwise, this line threw an error and there is no need to continue - frame.sv.static_typeof || break + frame.static_typeof || break changes = s[pc] end if frame.cur_hand !== () # propagate type info to exception handler l = frame.cur_hand[1] - if stchanged(changes, s[l], frame.sv.vars) + if stchanged(changes, s[l], frame.vars) push!(W, l) - s[l] = stupdate(s[l], changes, frame.sv.vars) + s[l] = stupdate(s[l], changes, frame.vars) end end pc´ = pc+1 @@ -1763,9 +1745,9 @@ function typeinf_frame(frame) changes = changes::StateUpdate id = (changes.var::GenSym).id + 1 new = changes.vtype.typ - old = frame.sv.gensym_types[id] + old = frame.gensym_types[id] if old===NF || !(new <: old) - frame.sv.gensym_types[id] = tmerge(old, new) + frame.gensym_types[id] = tmerge(old, new) for r in frame.gensym_uses[id] if !is(s[r], ()) # s[r] === () => unreached statement push!(W, r) @@ -1787,10 +1769,10 @@ function typeinf_frame(frame) else # general case frame.handler_at[l] = frame.cur_hand - if stchanged(changes, s[l], frame.sv.vars) + if stchanged(changes, s[l], frame.vars) # add else branch to active IP list push!(W, l) - s[l] = stupdate(s[l], changes, frame.sv.vars) + s[l] = stupdate(s[l], changes, frame.vars) end end elseif is(hd, :type_goto) @@ -1803,19 +1785,19 @@ function typeinf_frame(frame) # See issue #3821 (using !typeseq instead of !subtype), # and issue #7810. id = var.id+1 - vt = frame.sv.gensym_types[id] + vt = frame.gensym_types[id] ot = frame.gensym_init[id] if ot===NF || !typeseq(vt, ot) frame.gensym_init[id] = vt - if get(frame.sv.fedbackvars, var, false) + if get(frame.fedbackvars, var, false) frame.typegotoredo = true end end - frame.sv.fedbackvars[var] = true + frame.fedbackvars[var] = true end elseif is(hd, :return) pc´ = n + 1 - rt = abstract_eval(stmt.args[1], s[pc], frame.sv) + 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) @@ -1834,9 +1816,9 @@ function typeinf_frame(frame) l = frame.cur_hand[1] old = s[l] new = s[pc]::ObjectIdDict - if old === () || stchanged(new, old::ObjectIdDict, frame.sv.vars) + if old === () || stchanged(new, old::ObjectIdDict, frame.vars) push!(W, l) - s[l] = stupdate(old, new, frame.sv.vars) + s[l] = stupdate(old, new, frame.vars) end # if frame.handler_at[l] === 0 # frame.n_handlers += 1 @@ -1856,8 +1838,8 @@ function typeinf_frame(frame) end end if pc´<=n && (frame.handler_at[pc´] = frame.cur_hand; true) && - stchanged(changes, s[pc´], frame.sv.vars) - s[pc´] = stupdate(s[pc´], changes, frame.sv.vars) + stchanged(changes, s[pc´], frame.vars) + s[pc´] = stupdate(s[pc´], changes, frame.vars) pc = pc´ elseif pc´ in W pc = pc´ @@ -1894,7 +1876,7 @@ function typeinf_frame(frame) frame.cur_hand = () frame.handler_at = Any[ () for i=1:n ] frame.n_handlers = 0 - frame.sv.gensym_types[:] = frame.gensym_init + frame.gensym_types[:] = frame.gensym_init @goto restart_typeinf else # if a static_typeof was never reached, @@ -1902,9 +1884,9 @@ function typeinf_frame(frame) # running type inference from its uses # (one of which is the static_typeof) # TODO: this restart should happen just before calling finish() - for (fbvar, seen) in frame.sv.fedbackvars + for (fbvar, seen) in frame.fedbackvars if !seen - frame.sv.fedbackvars[fbvar] = true + frame.fedbackvars[fbvar] = true id = (fbvar::GenSym).id + 1 for r in frame.gensym_uses[id] if !is(s[r], ()) # s[r] === () => unreached statement @@ -1965,12 +1947,12 @@ function finish(me::InferenceState) me.inworkq = true # annotate fulltree with type information - for i = 1:length(me.sv.gensym_types) - if me.sv.gensym_types[i] === NF - me.sv.gensym_types[i] = Union{} + for i = 1:length(me.gensym_types) + if me.gensym_types[i] === NF + me.gensym_types[i] = Union{} end end - fulltree = type_annotate(me.sv.ast, me.stmt_types, me.sv, me.bestguess, me.args) + fulltree = type_annotate(me.ast, me.stmt_types, me, me.bestguess, me.args) # make sure (meta pure) is stripped from full tree @assert fulltree.args[3].head === :body @@ -1981,17 +1963,17 @@ function finish(me::InferenceState) # run optimization passes on fulltree if me.optimize if JLOptions().can_inline == 1 - fulltree.args[3] = inlining_pass(fulltree.args[3], me.sv, fulltree)[1] + fulltree.args[3] = inlining_pass(fulltree.args[3], me, fulltree)[1] # inlining can add variables - me.sv.vars = append_any(f_argnames(fulltree), map(vi->vi[1], fulltree.args[2][1])) + me.vars = append_any(f_argnames(fulltree), map(vi->vi[1], fulltree.args[2][1])) inbounds_meta_elim_pass(fulltree.args[3]) end - alloc_elim_pass(fulltree, me.sv) - getfield_elim_pass(fulltree.args[3], me.sv) + alloc_elim_pass(fulltree, me) + getfield_elim_pass(fulltree.args[3], me) end # finalize and record the linfo result - me.sv.ast = fulltree + me.ast = fulltree me.inferred = true compressedtree = ccall(:jl_compress_ast, Any, (Any,Any), me.linfo.def, fulltree) @@ -2031,7 +2013,7 @@ function record_var_type(e::Symbol, t::ANY, decls) end end -function eval_annotate(e::ANY, vtypes::ANY, sv::VarInfo, decls, undefs) +function eval_annotate(e::ANY, vtypes::ANY, sv::InferenceState, decls, undefs) if isa(e, Symbol) e = e::Symbol @@ -2219,7 +2201,7 @@ end const emptydict = ObjectIdDict() -function exprtype(x::ANY, sv::VarInfo) +function exprtype(x::ANY, sv::InferenceState) if isa(x,Expr) return (x::Expr).typ elseif isa(x,SymbolNode) @@ -2389,7 +2371,7 @@ end # static parameters are ok if all the static parameter values are leaf types, # meaning they are fully known. # `ft` is the type of the function. `f` is the exact function if known, or else `nothing`. -function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::VarInfo, enclosing_ast::Expr) +function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::InferenceState, enclosing_ast::Expr) local linfo, metharg::Type, argexprs = e.args, @@ -2967,7 +2949,7 @@ function mk_getfield(texpr, i, T) e end -function mk_tuplecall(args, sv::VarInfo) +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 @@ -3364,7 +3346,7 @@ symequal(x::Symbol , y::SymbolNode) = is(x,y.name) symequal(x::GenSym , y::GenSym) = is(x.id,y.id) symequal(x::ANY , y::ANY) = is(x,y) -function occurs_outside_getfield(e::ANY, sym::ANY, sv::VarInfo, field_count, field_names) +function occurs_outside_getfield(e::ANY, sym::ANY, sv::InferenceState, field_count, field_names) if is(e, sym) || (isa(e, SymbolNode) && is(e.name, sym)) return true end @@ -3452,7 +3434,7 @@ end # check if e is a successful allocation of an immutable struct # if it is, returns (n,f) such that it is always valid to call # getfield(..., 1 <= x <= n) or getfield(..., x in f) on the result -function is_immutable_allocation(e :: ANY, sv::VarInfo) +function is_immutable_allocation(e :: ANY, sv::InferenceState) isa(e, Expr) || return false if is_known_call(e, tuple, sv) return (length(e.args)-1,()) @@ -3476,7 +3458,7 @@ function is_immutable_allocation(e :: ANY, sv::VarInfo) end # eliminate allocation of unnecessary immutables # that are only used as arguments to safe getfield calls -function alloc_elim_pass(ast::Expr, sv::VarInfo) +function alloc_elim_pass(ast::Expr, sv::InferenceState) bexpr = ast.args[3]::Expr body = (ast.args[3].args)::Array{Any,1} vs = find_sa_vars(ast) From 79b08ca5db3711a8e32a8e7cbdeb51502c064c20 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 2 Mar 2016 16:05:46 -0500 Subject: [PATCH 6/7] prevent an item in the processing state from being recursively worked on the interaction here is rather complex, but recursing on a frame already in typeinf_frame causes the state upon return to that function to be a bit convoluted. detecting frame.inferred at that point and aborting was also correct, but requires a bit of code duplication at each abstract_interpret call to check and return quickly. making the processing state part of inworkq rather than stuck' should be a bit more robust against future edits to this code --- base/inference.jl | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 7d34abe1970b6..edd85b6d2e86b 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1675,7 +1675,6 @@ function typeinf_loop(frame) frame = active[end]::InferenceState else frame = pop!(workq) - frame.inworkq = false end typeinf_frame(frame) if isempty(workq) && nactive[] > 0 @@ -1684,7 +1683,10 @@ function typeinf_loop(frame) for i in active i === nothing && continue i = i::InferenceState - i.fixedpoint && finish(i) + if i.fixedpoint + i.inworkq = true + finish(i) + end end end end @@ -1705,6 +1707,8 @@ function typeinf_frame(frame) global global_sv # TODO: actually pass this to all functions that need it last_global_sv = global_sv global_sv = frame + frame.inworkq = true + @assert !frame.inferred W = frame.ip s = frame.stmt_types n = frame.nstmts @@ -1849,14 +1853,8 @@ function typeinf_frame(frame) end end - if frame.inferred - # during recursive compilation, this can happen. - # now just need to bail since it's already finished. - last_global_sv = global_sv - return - end - # with no active ip's, type inference on frame is done if there are no outstanding (unfinished) edges + @assert !frame.inferred finished = isempty(frame.edges) if isempty(workq) # oops, there's a cycle somewhere in the `edges` graph @@ -1911,6 +1909,7 @@ function typeinf_frame(frame) end end end + frame.inworkq = false global_sv = last_global_sv nothing end @@ -1944,7 +1943,7 @@ function finish(me::InferenceState) # see this InferenceState is in an imcomplete state # set `inworkq` to prevent it from trying to look # at the object in any detail - me.inworkq = true + @assert me.inworkq # annotate fulltree with type information for i = 1:length(me.gensym_types) From 53b02a69f7f9cc303551ead74f109cc964e639ab Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 3 Mar 2016 16:34:16 -0500 Subject: [PATCH 7/7] reenable inlining of special generic-functions these functions are intercepted during inference, giving the impression that this code path should be unreachable but actually they just need to be late-inferred so that they can be inlined --- base/inference.jl | 100 +++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 36 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index edd85b6d2e86b..2f6754c00816e 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1596,7 +1596,25 @@ function typeinf_edge(linfo::LambdaInfo, atypes::ANY, sparams::SimpleVector, nee # if the client was inlining, then this means we decided not to try to infer this # particular signature (due to signature coarsening in abstract_call_gf_by_type) # and attempting to force it now would be a bad idea (non terminating) - return (nothing, Union{}, false) + skip = true + if linfo.module == _topmod(linfo.module) || (isdefined(Main, :Base) && linfo.module == Main.Base) + # however, some gf have special tfunc and meaning they wouldn't have been inferred yet + # check the same conditions from abstract_call_gf to detect this case + if linfo.name == :promote_type || linfo.name == :typejoin + 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 || + (isa(argtypes[2], DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) && + (argtypes[2]::DataType).name === Main.Base.Pair.name)) + skip = false + end + end + end + if skip + return (nothing, Union{}, false) + end end # add lam to be inferred and record the edge ast = ccall(:jl_prepare_ast, Any, (Any,), linfo)::Expr @@ -1663,42 +1681,52 @@ function typeinf_loop(frame) frame.inworkq || typeinf_frame(frame) return end - in_typeinf_loop = true - # the core type-inference algorithm - # processes everything in workq, - # and returns when there is nothing left - while nactive[] > 0 - while active[end] === nothing - pop!(active) - end - if isempty(workq) - frame = active[end]::InferenceState - else - frame = pop!(workq) - end - typeinf_frame(frame) - if isempty(workq) && nactive[] > 0 - # nothing in active has an edge that hasn't reached a fixed-point - # so all of them can be considered finished now - for i in active - i === nothing && continue - i = i::InferenceState - if i.fixedpoint - i.inworkq = true - finish(i) + try + in_typeinf_loop = true + # the core type-inference algorithm + # processes everything in workq, + # and returns when there is nothing left + while nactive[] > 0 + while active[end] === nothing + pop!(active) + end + if isempty(workq) + frame = active[end]::InferenceState + else + frame = pop!(workq) + end + typeinf_frame(frame) + if isempty(workq) && nactive[] > 0 + # nothing in active has an edge that hasn't reached a fixed-point + # so all of them can be considered finished now + fplist = Any[] + for i in active + i === nothing && continue + i = i::InferenceState + if i.fixedpoint + push!(fplist, i) + i.inworkq = true + end + end + for i in fplist + finish(i) # this may add incomplete work to active end end end + # cleanup the active queue + empty!(active) + # while active[end] === nothing + # # this pops everything, but with exaggerated care just in case + # # something managed to add something to the queue at the same time + # # (or someone decides to use an alternative termination condition) + # pop!(active) + # end + in_typeinf_loop = false + catch ex + println("WARNING: An error occured during inference. Type inference is now partially disabled.") + println(ex) + ccall(:jlbacktrace, Void, ()) end - # cleanup the active queue - empty!(active) -# while active[end] === nothing -# # this pops everything, but with exaggerated care just in case -# # something managed to add something to the queue at the same time -# # (or someone decides to use an alternative termination condition) -# pop!(active) -# end - in_typeinf_loop = false nothing end @@ -1707,8 +1735,8 @@ function typeinf_frame(frame) global global_sv # TODO: actually pass this to all functions that need it last_global_sv = global_sv global_sv = frame - frame.inworkq = true @assert !frame.inferred + frame.inworkq = true W = frame.ip s = frame.stmt_types n = frame.nstmts @@ -1940,7 +1968,7 @@ function finish(me::InferenceState) @assert (i::InferenceState).fixedpoint end # below may call back into inference and - # see this InferenceState is in an imcomplete state + # see this InferenceState is in an incomplete state # set `inworkq` to prevent it from trying to look # at the object in any detail @assert me.inworkq @@ -2407,7 +2435,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference local methfunc atype = Tuple{atypes...} - if length(atype.parameters)-1 > MAX_TUPLETYPE_LEN + if length(atype.parameters) - 1 > MAX_TUPLETYPE_LEN atype = limit_tuple_type(atype) end meth = _methods_by_ftype(atype, 1)