diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 2ec3d76f6cbfe..572ad43b72e51 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -4,6 +4,8 @@ # constants # ############# +const DEFAULT_INTERPRETER = NativeInterpreter(UInt(0)) + const CoreNumType = Union{Int32, Int64, Float32, Float64} const _REF_NAME = Ref.body.name @@ -16,8 +18,8 @@ const _REF_NAME = Ref.body.name call_result_unused(frame::InferenceState, pc::LineNum=frame.currpc) = isexpr(frame.src.code[frame.currpc], :call) && isempty(frame.ssavalue_uses[pc]) -function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nospecialize(atype), sv::InferenceState, - max_methods = sv.params.MAX_METHODS) +function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any}, @nospecialize(atype), sv::InferenceState, + max_methods = InferenceParams(interp).MAX_METHODS) atype_params = unwrap_unionall(atype).parameters ft = unwrap_unionall(atype_params[1]) # TODO: ccall jl_method_table_for here isa(ft, DataType) || return Any # the function being called is unknown. can't properly handle this backedge right now @@ -37,15 +39,15 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp end min_valid = UInt[typemin(UInt)] max_valid = UInt[typemax(UInt)] - splitunions = 1 < countunionsplit(atype_params) <= sv.params.MAX_UNION_SPLITTING + splitunions = 1 < countunionsplit(atype_params) <= InferenceParams(interp).MAX_UNION_SPLITTING if splitunions splitsigs = switchtupleunion(atype) applicable = Any[] for sig_n in splitsigs (xapplicable, min_valid[1], max_valid[1]) = get!(sv.matching_methods_cache, sig_n) do - ms = _methods_by_ftype(sig_n, max_methods, sv.params.world, - min_valid, max_valid) + ms = _methods_by_ftype(sig_n, max_methods, + get_world_counter(interp), min_valid, max_valid) return (ms, min_valid[1], max_valid[1]) end xapplicable === false && return Any @@ -54,8 +56,8 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp else (applicable, min_valid[1], max_valid[1]) = get!(sv.matching_methods_cache, atype) do - ms = _methods_by_ftype(atype, max_methods, sv.params.world, - min_valid, max_valid) + ms = _methods_by_ftype(atype, max_methods, + get_world_counter(interp), min_valid, max_valid) return (ms, min_valid[1], max_valid[1]) end if applicable === false @@ -94,12 +96,12 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp sigtuple = unwrap_unionall(sig)::DataType splitunions = false this_rt = Bottom - # TODO: splitunions = 1 < countunionsplit(sigtuple.parameters) * napplicable <= sv.params.MAX_UNION_SPLITTING + # TODO: splitunions = 1 < countunionsplit(sigtuple.parameters) * napplicable <= InferenceParams(interp).MAX_UNION_SPLITTING # currently this triggers a bug in inference recursion detection if splitunions splitsigs = switchtupleunion(sig) for sig_n in splitsigs - rt, edgecycle1, edge = abstract_call_method(method, sig_n, svec(), multiple_matches, sv) + rt, edgecycle1, edge = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, sv) if edge !== nothing push!(edges, edge) end @@ -108,7 +110,7 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp this_rt === Any && break end else - this_rt, edgecycle1, edge = abstract_call_method(method, sig, match[2]::SimpleVector, multiple_matches, sv) + this_rt, edgecycle1, edge = abstract_call_method(interp, method, sig, match[2]::SimpleVector, multiple_matches, sv) edgecycle |= edgecycle1::Bool if edge !== nothing push!(edges, edge) @@ -127,11 +129,11 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp end # try constant propagation if only 1 method is inferred to non-Bottom # this is in preparation for inlining, or improving the return result - if nonbot > 0 && seen == napplicable && !edgecycle && isa(rettype, Type) && sv.params.ipo_constant_propagation + if nonbot > 0 && seen == napplicable && !edgecycle && isa(rettype, Type) && InferenceParams(interp).ipo_constant_propagation # if there's a possibility we could constant-propagate a better result # (hopefully without doing too much work), try to do that now # TODO: it feels like this could be better integrated into abstract_call_method / typeinf_edge - const_rettype = abstract_call_method_with_const_args(rettype, f, argtypes, applicable[nonbot]::SimpleVector, sv) + const_rettype = abstract_call_method_with_const_args(interp, rettype, f, argtypes, applicable[nonbot]::SimpleVector, sv) if const_rettype ⊑ rettype # use the better result, if it's a refinement of rettype rettype = const_rettype @@ -184,7 +186,7 @@ function const_prop_profitable(@nospecialize(arg)) return false end -function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecialize(f), argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState) +function abstract_call_method_with_const_args(interp::AbstractInterpreter, @nospecialize(rettype), @nospecialize(f), argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState) method = match[3]::Method nargs::Int = method.nargs method.isva && (nargs -= 1) @@ -229,7 +231,7 @@ function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecial istopfunction(f, :<<) || istopfunction(f, :>>)) return Any end - force_inference = allconst || sv.params.aggressive_constant_propagation + force_inference = allconst || InferenceParams(interp).aggressive_constant_propagation if istopfunction(f, :getproperty) || istopfunction(f, :setproperty!) force_inference = true end @@ -240,7 +242,7 @@ function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecial mi = mi::MethodInstance # decide if it's likely to be worthwhile if !force_inference - code = inf_for_methodinstance(mi, sv.params.world) + code = inf_for_methodinstance(interp, mi, get_world_counter(interp)) declared_inline = isdefined(method, :source) && ccall(:jl_ir_flag_inlineable, Bool, (Any,), method.source) cache_inlineable = declared_inline if isdefined(code, :inferred) && !cache_inlineable @@ -255,14 +257,15 @@ function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecial return Any end end - inf_result = cache_lookup(mi, argtypes, sv.params.cache) + inf_cache = get_inference_cache(interp) + inf_result = cache_lookup(mi, argtypes, inf_cache) if inf_result === nothing inf_result = InferenceResult(mi, argtypes) - frame = InferenceState(inf_result, #=cache=#false, sv.params) + frame = InferenceState(inf_result, #=cache=#false, interp) frame.limited = true frame.parent = sv - push!(sv.params.cache, inf_result) - typeinf(frame) || return Any + push!(inf_cache, inf_result) + typeinf(interp, frame) || return Any end result = inf_result.result isa(result, InferenceState) && return Any # TODO: unexpected, is this recursive constant inference? @@ -270,7 +273,7 @@ function abstract_call_method_with_const_args(@nospecialize(rettype), @nospecial return result end -function abstract_call_method(method::Method, @nospecialize(sig), sparams::SimpleVector, hardlimit::Bool, sv::InferenceState) +function abstract_call_method(interp::AbstractInterpreter, method::Method, @nospecialize(sig), sparams::SimpleVector, hardlimit::Bool, sv::InferenceState) if method.name === :depwarn && isdefined(Main, :Base) && method.module === Main.Base return Any, false, nothing end @@ -370,7 +373,7 @@ function abstract_call_method(method::Method, @nospecialize(sig), sparams::Simpl comparison = method.sig end # see if the type is actually too big (relative to the caller), and limit it if required - newsig = limit_type_size(sig, comparison, hardlimit ? comparison : sv.linfo.specTypes, sv.params.TUPLE_COMPLEXITY_LIMIT_DEPTH, spec_len) + newsig = limit_type_size(sig, comparison, hardlimit ? comparison : sv.linfo.specTypes, InferenceParams(interp).TUPLE_COMPLEXITY_LIMIT_DEPTH, spec_len) if newsig !== sig # continue inference, but note that we've limited parameter complexity @@ -407,7 +410,7 @@ function abstract_call_method(method::Method, @nospecialize(sig), sparams::Simpl # while !(newsig in seen) # push!(seen, newsig) # lsig = length((unwrap_unionall(sig)::DataType).parameters) - # newsig = limit_type_size(newsig, sig, sv.linfo.specTypes, sv.params.TUPLE_COMPLEXITY_LIMIT_DEPTH, lsig) + # newsig = limit_type_size(newsig, sig, sv.linfo.specTypes, InferenceParams(interp).TUPLE_COMPLEXITY_LIMIT_DEPTH, lsig) # recomputed = ccall(:jl_type_intersection_with_env, Any, (Any, Any), newsig, method.sig)::SimpleVector # newsig = recomputed[2] # end @@ -415,7 +418,7 @@ function abstract_call_method(method::Method, @nospecialize(sig), sparams::Simpl sparams = recomputed[2]::SimpleVector end - rt, edge = typeinf_edge(method, sig, sparams, sv) + rt, edge = typeinf_edge(interp, method, sig, sparams, sv) if edge === nothing edgecycle = true end @@ -449,7 +452,7 @@ end # refine its type to an array of element types. # Union of Tuples of the same length is converted to Tuple of Unions. # returns an array of types -function precise_container_type(@nospecialize(itft), @nospecialize(typ), vtypes::VarTable, sv::InferenceState) +function precise_container_type(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(typ), vtypes::VarTable, sv::InferenceState) if isa(typ, PartialStruct) && typ.typ.name === Tuple.name return typ.fields end @@ -511,12 +514,12 @@ function precise_container_type(@nospecialize(itft), @nospecialize(typ), vtypes: elseif tti0 <: Array return Any[Vararg{eltype(tti0)}] else - return abstract_iteration(itft, typ, vtypes, sv) + return abstract_iteration(interp, itft, typ, vtypes, sv) end end # simulate iteration protocol on container type up to fixpoint -function abstract_iteration(@nospecialize(itft), @nospecialize(itertype), vtypes::VarTable, sv::InferenceState) +function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(itertype), vtypes::VarTable, sv::InferenceState) if !isdefined(Main, :Base) || !isdefined(Main.Base, :iterate) || !isconst(Main.Base, :iterate) return Any[Vararg{Any}] end @@ -528,7 +531,7 @@ function abstract_iteration(@nospecialize(itft), @nospecialize(itertype), vtypes else return Any[Vararg{Any}] end - stateordonet = abstract_call_known(iteratef, nothing, Any[itft, itertype], vtypes, sv) + stateordonet = abstract_call_known(interp, iteratef, nothing, Any[itft, itertype], vtypes, sv) # Return Bottom if this is not an iterator. # WARNING: Changes to the iteration protocol must be reflected here, # this is not just an optimization. @@ -536,7 +539,7 @@ function abstract_iteration(@nospecialize(itft), @nospecialize(itertype), vtypes valtype = statetype = Bottom ret = Any[] stateordonet = widenconst(stateordonet) - while !(Nothing <: stateordonet) && length(ret) < sv.params.MAX_TUPLE_SPLAT + while !(Nothing <: stateordonet) && length(ret) < InferenceParams(interp).MAX_TUPLE_SPLAT if !isa(stateordonet, DataType) || !(stateordonet <: Tuple) || isvatuple(stateordonet) || length(stateordonet.parameters) != 2 break end @@ -547,7 +550,7 @@ function abstract_iteration(@nospecialize(itft), @nospecialize(itertype), vtypes valtype = stateordonet.parameters[1] statetype = stateordonet.parameters[2] push!(ret, valtype) - stateordonet = abstract_call_known(iteratef, nothing, Any[Const(iteratef), itertype, statetype], vtypes, sv) + stateordonet = abstract_call_known(interp, iteratef, nothing, Any[Const(iteratef), itertype, statetype], vtypes, sv) stateordonet = widenconst(stateordonet) end if stateordonet === Nothing @@ -564,7 +567,7 @@ function abstract_iteration(@nospecialize(itft), @nospecialize(itertype), vtypes end valtype = tmerge(valtype, nounion.parameters[1]) statetype = tmerge(statetype, nounion.parameters[2]) - stateordonet = abstract_call_known(iteratef, nothing, Any[Const(iteratef), itertype, statetype], vtypes, sv) + stateordonet = abstract_call_known(interp, iteratef, nothing, Any[Const(iteratef), itertype, statetype], vtypes, sv) stateordonet = widenconst(stateordonet) end push!(ret, Vararg{valtype}) @@ -572,8 +575,8 @@ function abstract_iteration(@nospecialize(itft), @nospecialize(itertype), vtypes end # do apply(af, fargs...), where af is a function value -function abstract_apply(@nospecialize(itft), @nospecialize(aft), aargtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, - max_methods = sv.params.MAX_METHODS) +function abstract_apply(interp::AbstractInterpreter, @nospecialize(itft), @nospecialize(aft), aargtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, + max_methods = InferenceParams(interp).MAX_METHODS) aftw = widenconst(aft) if !isa(aft, Const) && (!isType(aftw) || has_free_typevars(aftw)) if !isconcretetype(aftw) || (aftw <: Builtin) @@ -585,12 +588,12 @@ function abstract_apply(@nospecialize(itft), @nospecialize(aft), aargtypes::Vect end res = Union{} nargs = length(aargtypes) - splitunions = 1 < countunionsplit(aargtypes) <= sv.params.MAX_APPLY_UNION_ENUM + splitunions = 1 < countunionsplit(aargtypes) <= InferenceParams(interp).MAX_APPLY_UNION_ENUM ctypes = Any[Any[aft]] for i = 1:nargs ctypes´ = [] for ti in (splitunions ? uniontypes(aargtypes[i]) : Any[aargtypes[i]]) - cti = precise_container_type(itft, ti, vtypes, sv) + cti = precise_container_type(interp, itft, ti, vtypes, sv) if _any(t -> t === Bottom, cti) continue end @@ -615,7 +618,7 @@ function abstract_apply(@nospecialize(itft), @nospecialize(aft), aargtypes::Vect break end end - rt = abstract_call(nothing, ct, vtypes, sv, max_methods) + rt = abstract_call(interp, nothing, ct, vtypes, sv, max_methods) res = tmerge(res, rt) if res === Any break @@ -672,19 +675,19 @@ function argtype_tail(argtypes::Vector{Any}, i::Int) end # call where the function is known exactly -function abstract_call_known(@nospecialize(f), fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, max_methods = sv.params.MAX_METHODS) +function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, max_methods = InferenceParams(interp).MAX_METHODS) la = length(argtypes) if isa(f, Builtin) if f === _apply ft = argtype_by_index(argtypes, 2) ft === Bottom && return Bottom - return abstract_apply(nothing, ft, argtype_tail(argtypes, 3), vtypes, sv, max_methods) + return abstract_apply(interp, nothing, ft, argtype_tail(argtypes, 3), vtypes, sv, max_methods) elseif f === _apply_iterate itft = argtype_by_index(argtypes, 2) ft = argtype_by_index(argtypes, 3) (itft === Bottom || ft === Bottom) && return Bottom - return abstract_apply(itft, ft, argtype_tail(argtypes, 4), vtypes, sv, max_methods) + return abstract_apply(interp, itft, ft, argtype_tail(argtypes, 4), vtypes, sv, max_methods) elseif f === ifelse && fargs isa Vector{Any} && la == 4 && argtypes[2] isa Conditional # try to simulate this as a real conditional (`cnd ? x : y`), so that the penalty for using `ifelse` instead isn't too high cnd = argtypes[2]::Conditional @@ -700,9 +703,9 @@ function abstract_call_known(@nospecialize(f), fargs::Union{Nothing,Vector{Any}} end return tmerge(tx, ty) end - rt = builtin_tfunction(f, argtypes[2:end], sv) + rt = builtin_tfunction(interp, f, argtypes[2:end], sv) if f === getfield && isa(fargs, Vector{Any}) && la == 3 && isa(argtypes[3], Const) && isa(argtypes[3].val, Int) && argtypes[2] ⊑ Tuple - cti = precise_container_type(nothing, argtypes[2], vtypes, sv) + cti = precise_container_type(interp, nothing, argtypes[2], vtypes, sv) idx = argtypes[3].val if 1 <= idx <= length(cti) rt = unwrapva(cti[idx]) @@ -831,7 +834,7 @@ function abstract_call_known(@nospecialize(f), fargs::Union{Nothing,Vector{Any}} elseif f === Tuple && la == 2 && !isconcretetype(widenconst(argtypes[2])) return Tuple elseif is_return_type(f) - rt_rt = return_type_tfunc(argtypes, vtypes, sv) + rt_rt = return_type_tfunc(interp, argtypes, vtypes, sv) if rt_rt !== nothing return rt_rt end @@ -840,12 +843,12 @@ function abstract_call_known(@nospecialize(f), fargs::Union{Nothing,Vector{Any}} # handle Conditional propagation through !Bool aty = argtypes[2] if isa(aty, Conditional) - abstract_call_gf_by_type(f, Any[Const(f), Bool], Tuple{typeof(f), Bool}, sv) # make sure we've inferred `!(::Bool)` + abstract_call_gf_by_type(interp, f, Any[Const(f), Bool], Tuple{typeof(f), Bool}, sv) # make sure we've inferred `!(::Bool)` return Conditional(aty.var, aty.elsetype, aty.vtype) end elseif la == 3 && istopfunction(f, :!==) # mark !== as exactly a negated call to === - rty = abstract_call_known((===), fargs, argtypes, vtypes, sv) + rty = abstract_call_known(interp, (===), fargs, argtypes, vtypes, sv) if isa(rty, Conditional) return Conditional(rty.var, rty.elsetype, rty.vtype) # swap if-else elseif isa(rty, Const) @@ -861,7 +864,7 @@ function abstract_call_known(@nospecialize(f), fargs::Union{Nothing,Vector{Any}} fargs = nothing end argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] - rty = abstract_call_known(<:, fargs, argtypes, vtypes, sv) + rty = abstract_call_known(interp, <:, fargs, argtypes, vtypes, sv) return rty elseif la == 2 && isa(argtypes[2], Const) && isa(argtypes[2].val, SimpleVector) && istopfunction(f, :length) # mark length(::SimpleVector) as @pure @@ -884,12 +887,12 @@ function abstract_call_known(@nospecialize(f), fargs::Union{Nothing,Vector{Any}} end atype = argtypes_to_type(argtypes) - return abstract_call_gf_by_type(f, argtypes, atype, sv, max_methods) + return abstract_call_gf_by_type(interp, f, argtypes, atype, sv, max_methods) end # call where the function is any lattice element -function abstract_call(fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState, - max_methods = sv.params.MAX_METHODS) +function abstract_call(interp::AbstractInterpreter, fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, + vtypes::VarTable, sv::InferenceState, max_methods = InferenceParams(interp).MAX_METHODS) #print("call ", e.args[1], argtypes, "\n\n") ft = argtypes[1] if isa(ft, Const) @@ -904,9 +907,9 @@ function abstract_call(fargs::Union{Nothing,Vector{Any}}, argtypes::Vector{Any}, if typeintersect(widenconst(ft), Builtin) != Union{} return Any end - return abstract_call_gf_by_type(nothing, argtypes, argtypes_to_type(argtypes), sv, max_methods) + return abstract_call_gf_by_type(interp, nothing, argtypes, argtypes_to_type(argtypes), sv, max_methods) end - return abstract_call_known(f, fargs, argtypes, vtypes, sv, max_methods) + return abstract_call_known(interp, f, fargs, argtypes, vtypes, sv, max_methods) end function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool) @@ -947,19 +950,19 @@ function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool) return T end -function abstract_eval_cfunction(e::Expr, vtypes::VarTable, sv::InferenceState) - f = abstract_eval(e.args[2], vtypes, sv) +function abstract_eval_cfunction(interp::AbstractInterpreter, e::Expr, vtypes::VarTable, sv::InferenceState) + f = abstract_eval(interp, e.args[2], vtypes, sv) # rt = sp_type_rewrap(e.args[3], sv.linfo, true) at = Any[ sp_type_rewrap(argt, sv.linfo, false) for argt in e.args[4]::SimpleVector ] pushfirst!(at, f) # this may be the wrong world for the call, # but some of the result is likely to be valid anyways # and that may help generate better codegen - abstract_call(nothing, at, vtypes, sv) + abstract_call(interp, nothing, at, vtypes, sv) nothing end -function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) +function abstract_eval(interp::AbstractInterpreter, @nospecialize(e), vtypes::VarTable, sv::InferenceState) if isa(e, QuoteNode) return AbstractEvalConstant((e::QuoteNode).value) elseif isa(e, SSAValue) @@ -979,22 +982,22 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) n = length(ea) argtypes = Vector{Any}(undef, n) @inbounds for i = 1:n - ai = abstract_eval(ea[i], vtypes, sv) + ai = abstract_eval(interp, ea[i], vtypes, sv) if ai === Bottom return Bottom end argtypes[i] = ai end - t = abstract_call(ea, argtypes, vtypes, sv) + t = abstract_call(interp, ea, argtypes, vtypes, sv) elseif e.head === :new - t = instanceof_tfunc(abstract_eval(e.args[1], vtypes, sv))[1] + t = instanceof_tfunc(abstract_eval(interp, e.args[1], vtypes, sv))[1] if isconcretetype(t) && !t.mutable args = Vector{Any}(undef, length(e.args)-1) ats = Vector{Any}(undef, length(e.args)-1) anyconst = false allconst = true for i = 2:length(e.args) - at = abstract_eval(e.args[i], vtypes, sv) + at = abstract_eval(interp, e.args[i], vtypes, sv) if !anyconst anyconst = has_nontrivial_const_info(at) end @@ -1024,22 +1027,22 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) end end elseif e.head === :splatnew - t = instanceof_tfunc(abstract_eval(e.args[1], vtypes, sv))[1] + t = instanceof_tfunc(abstract_eval(interp, e.args[1], vtypes, sv))[1] elseif e.head === :& - abstract_eval(e.args[1], vtypes, sv) + abstract_eval(interp, e.args[1], vtypes, sv) t = Any elseif e.head === :foreigncall - abstract_eval(e.args[1], vtypes, sv) + abstract_eval(interp, e.args[1], vtypes, sv) t = sp_type_rewrap(e.args[2], sv.linfo, true) for i = 3:length(e.args) - if abstract_eval(e.args[i], vtypes, sv) === Bottom + if abstract_eval(interp, e.args[i], vtypes, sv) === Bottom t = Bottom end end elseif e.head === :cfunction t = e.args[1] isa(t, Type) || (t = Any) - abstract_eval_cfunction(e, vtypes, sv) + abstract_eval_cfunction(interp, e, vtypes, sv) elseif e.head === :static_parameter n = e.args[1] t = Any @@ -1049,7 +1052,7 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState) elseif e.head === :method t = (length(e.args) == 1) ? Any : Nothing elseif e.head === :copyast - t = abstract_eval(e.args[1], vtypes, sv) + t = abstract_eval(interp, e.args[1], vtypes, sv) if t isa Const && t.val isa Expr # `copyast` makes copies of Exprs t = Expr @@ -1112,7 +1115,7 @@ function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo) end # make as much progress on `frame` as possible (without handling cycles) -function typeinf_local(frame::InferenceState) +function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !frame.inferred frame.dont_work_on_me = true # mark that this function is currently on the stack W = frame.ip @@ -1145,7 +1148,7 @@ function typeinf_local(frame::InferenceState) elseif isa(stmt, GotoNode) pc´ = (stmt::GotoNode).label elseif hd === :gotoifnot - condt = abstract_eval(stmt.args[1], s[pc], frame) + condt = abstract_eval(interp, stmt.args[1], s[pc], frame) if condt === Bottom break end @@ -1179,7 +1182,7 @@ function typeinf_local(frame::InferenceState) end elseif hd === :return pc´ = n + 1 - rt = widenconditional(abstract_eval(stmt.args[1], s[pc], frame)) + rt = widenconditional(abstract_eval(interp, stmt.args[1], s[pc], frame)) if !isa(rt, Const) && !isa(rt, Type) && !isa(rt, PartialStruct) # only propagate information we know we can store # and is valid inter-procedurally @@ -1224,7 +1227,7 @@ function typeinf_local(frame::InferenceState) end else if hd === :(=) - t = abstract_eval(stmt.args[2], changes, frame) + t = abstract_eval(interp, stmt.args[2], changes, frame) t === Bottom && break frame.src.ssavaluetypes[pc] = t lhs = stmt.args[1] @@ -1239,7 +1242,7 @@ function typeinf_local(frame::InferenceState) elseif hd === :inbounds || hd === :meta || hd === :loopinfo || hd == :code_coverage_effect # these do not generate code else - t = abstract_eval(stmt, changes, frame) + t = abstract_eval(interp, stmt, changes, frame) t === Bottom && break if !isempty(frame.ssavalue_uses[pc]) record_ssa_assign(pc, t, frame) @@ -1294,8 +1297,8 @@ function typeinf_local(frame::InferenceState) end # make as much progress on `frame` as possible (by handling cycles) -function typeinf_nocycle(frame::InferenceState) - typeinf_local(frame) +function typeinf_nocycle(interp::AbstractInterpreter, frame::InferenceState) + typeinf_local(interp, frame) # If the current frame is part of a cycle, solve the cycle before finishing no_active_ips_in_callers = false @@ -1304,10 +1307,10 @@ function typeinf_nocycle(frame::InferenceState) for caller in frame.callers_in_cycle caller.dont_work_on_me && return false # cycle is above us on the stack if caller.pc´´ <= caller.nstmts # equivalent to `isempty(caller.ip)` - # Note that `typeinf_local(caller)` can potentially modify the other frames + # Note that `typeinf_local(interp, caller)` can potentially modify the other frames # `frame.callers_in_cycle`, which is why making incremental progress requires the # outer while loop. - typeinf_local(caller) + typeinf_local(interp, caller) no_active_ips_in_callers = false end if caller.min_valid < frame.min_valid diff --git a/base/compiler/bootstrap.jl b/base/compiler/bootstrap.jl index b1fb73f6b659e..54a8e26ced9da 100644 --- a/base/compiler/bootstrap.jl +++ b/base/compiler/bootstrap.jl @@ -6,7 +6,9 @@ # since we won't be able to specialize & infer them at runtime let fs = Any[typeinf_ext, typeinf, typeinf_edge, pure_eval_call, run_passes], - world = get_world_counter() + world = get_world_counter(), + interp = NativeInterpreter(world) + for x in T_FFUNC_VAL push!(fs, x[3]) end @@ -27,7 +29,7 @@ let fs = Any[typeinf_ext, typeinf, typeinf_edge, pure_eval_call, run_passes], typ[i] = typ[i].ub end end - typeinf_type(m[3], Tuple{typ...}, m[2], Params(world)) + typeinf_type(interp, m[3], Tuple{typ...}, m[2]) end end end diff --git a/base/compiler/compiler.jl b/base/compiler/compiler.jl index 039f28de828be..c8356611da4ab 100644 --- a/base/compiler/compiler.jl +++ b/base/compiler/compiler.jl @@ -94,11 +94,11 @@ using .Sort # compiler # ############ +include("compiler/types.jl") include("compiler/utilities.jl") include("compiler/validation.jl") include("compiler/inferenceresult.jl") -include("compiler/params.jl") include("compiler/inferencestate.jl") include("compiler/typeutils.jl") @@ -111,7 +111,7 @@ include("compiler/typeinfer.jl") include("compiler/optimize.jl") # TODO: break this up further + extract utilities include("compiler/bootstrap.jl") -ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext) +ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_ext_toplevel) end # baremodule Compiler )) diff --git a/base/compiler/inferenceresult.jl b/base/compiler/inferenceresult.jl index 24206369b3018..89176b54ec93b 100644 --- a/base/compiler/inferenceresult.jl +++ b/base/compiler/inferenceresult.jl @@ -2,18 +2,6 @@ const EMPTY_VECTOR = Vector{Any}() -mutable struct InferenceResult - linfo::MethodInstance - argtypes::Vector{Any} - overridden_by_const::BitVector - result # ::Type, or InferenceState if WIP - src #::Union{CodeInfo, OptimizationState, Nothing} # if inferred copy is available - function InferenceResult(linfo::MethodInstance, given_argtypes = nothing) - argtypes, overridden_by_const = matching_cache_argtypes(linfo, given_argtypes) - return new(linfo, argtypes, overridden_by_const, Any, nothing) - end -end - function is_argtype_match(@nospecialize(given_argtype), @nospecialize(cache_argtype), overridden_by_const::Bool) diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index c6ac979abf96d..788bf3598551a 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -3,7 +3,7 @@ const LineNum = Int mutable struct InferenceState - params::Params # describes how to compute the result + params::InferenceParams result::InferenceResult # remember where to put the result linfo::MethodInstance sptypes::Vector{Any} # types of static parameter @@ -13,6 +13,7 @@ mutable struct InferenceState # info on the state of inference and the linfo src::CodeInfo + world::UInt min_valid::UInt max_valid::UInt nargs::Int @@ -47,7 +48,7 @@ mutable struct InferenceState # src is assumed to be a newly-allocated CodeInfo, that can be modified in-place to contain intermediate results function InferenceState(result::InferenceResult, src::CodeInfo, - cached::Bool, params::Params) + cached::Bool, interp::AbstractInterpreter) linfo = result.linfo code = src.code::Array{Any,1} toplevel = !isa(linfo.def, Method) @@ -95,9 +96,9 @@ mutable struct InferenceState max_valid = src.max_world == typemax(UInt) ? get_world_counter() : src.max_world frame = new( - params, result, linfo, + InferenceParams(interp), result, linfo, sp, slottypes, inmodule, 0, - src, min_valid, max_valid, + src, get_world_counter(interp), min_valid, max_valid, nargs, s_types, s_edges, Union{}, W, 1, n, cur_hand, handler_at, n_handlers, @@ -108,17 +109,17 @@ mutable struct InferenceState cached, false, false, false, IdDict{Any, Tuple{Any, UInt, UInt}}()) result.result = frame - cached && push!(params.cache, result) + cached && push!(get_inference_cache(interp), result) return frame end end -function InferenceState(result::InferenceResult, cached::Bool, params::Params) +function InferenceState(result::InferenceResult, cached::Bool, interp::AbstractInterpreter) # prepare an InferenceState object for inferring lambda src = retrieve_code_info(result.linfo) src === nothing && return nothing validate_code_in_debug_mode(result.linfo, src, "lowered") - return InferenceState(result, src, cached, params) + return InferenceState(result, src, cached, interp) end function sptypes_from_meth_instance(linfo::MethodInstance) @@ -195,8 +196,7 @@ _topmod(sv::InferenceState) = _topmod(sv.mod) function update_valid_age!(min_valid::UInt, max_valid::UInt, sv::InferenceState) sv.min_valid = max(sv.min_valid, min_valid) sv.max_valid = min(sv.max_valid, max_valid) - @assert(sv.min_valid <= sv.params.world <= sv.max_valid, - "invalid age range update") + @assert(sv.min_valid <= sv.world <= sv.max_valid, "invalid age range update") nothing end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index b1cc75da70942..b0aa0423f030b 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -5,36 +5,38 @@ ##################### mutable struct OptimizationState + params::OptimizationParams linfo::MethodInstance calledges::Vector{Any} src::CodeInfo mod::Module nargs::Int + world::UInt min_valid::UInt max_valid::UInt - params::Params sptypes::Vector{Any} # static parameters slottypes::Vector{Any} const_api::Bool # cached results of calling `_methods_by_ftype` from inference, including # `min_valid` and `max_valid` matching_methods_cache::IdDict{Any, Tuple{Any, UInt, UInt}} - function OptimizationState(frame::InferenceState) + # TODO: This will be eliminated once optimization no longer needs to do method lookups + interp::AbstractInterpreter + function OptimizationState(frame::InferenceState, params::OptimizationParams, interp::AbstractInterpreter) s_edges = frame.stmt_edges[1] if s_edges === nothing s_edges = [] frame.stmt_edges[1] = s_edges end src = frame.src - return new(frame.linfo, + return new(params, frame.linfo, s_edges::Vector{Any}, src, frame.mod, frame.nargs, - frame.min_valid, frame.max_valid, - frame.params, frame.sptypes, frame.slottypes, false, - frame.matching_methods_cache) + frame.world, frame.min_valid, frame.max_valid, + frame.sptypes, frame.slottypes, false, + frame.matching_methods_cache, interp) end - function OptimizationState(linfo::MethodInstance, src::CodeInfo, - params::Params) + function OptimizationState(linfo::MethodInstance, src::CodeInfo, params::OptimizationParams, interp::AbstractInterpreter) # prepare src for running optimization passes # if it isn't already nssavalues = src.ssavaluetypes @@ -57,19 +59,19 @@ mutable struct OptimizationState inmodule = linfo.def::Module nargs = 0 end - return new(linfo, + return new(params, linfo, s_edges::Vector{Any}, src, inmodule, nargs, - UInt(1), get_world_counter(), - params, sptypes_from_meth_instance(linfo), slottypes, false, - IdDict{Any, Tuple{Any, UInt, UInt}}()) + get_world_counter(), UInt(1), get_world_counter(), + sptypes_from_meth_instance(linfo), slottypes, false, + IdDict{Any, Tuple{Any, UInt, UInt}}(), interp) end end -function OptimizationState(linfo::MethodInstance, params::Params) +function OptimizationState(linfo::MethodInstance, params::OptimizationParams, interp::AbstractInterpreter) src = retrieve_code_info(linfo) src === nothing && return nothing - return OptimizationState(linfo, src, params) + return OptimizationState(linfo, src, params, interp) end @@ -109,7 +111,7 @@ _topmod(sv::OptimizationState) = _topmod(sv.mod) function update_valid_age!(min_valid::UInt, max_valid::UInt, sv::OptimizationState) sv.min_valid = max(sv.min_valid, min_valid) sv.max_valid = min(sv.max_valid, max_valid) - @assert(sv.min_valid <= sv.params.world <= sv.max_valid, + @assert(sv.min_valid <= sv.world <= sv.max_valid, "invalid age range update") nothing end @@ -127,10 +129,10 @@ function add_backedge!(li::CodeInstance, caller::OptimizationState) nothing end -function isinlineable(m::Method, me::OptimizationState, bonus::Int=0) +function isinlineable(m::Method, me::OptimizationState, params::OptimizationParams, bonus::Int=0) # compute the cost (size) of inlining this code inlineable = false - cost_threshold = me.params.inline_cost_threshold + cost_threshold = params.inline_cost_threshold if m.module === _topmod(m.module) # a few functions get special treatment name = m.name @@ -145,7 +147,7 @@ function isinlineable(m::Method, me::OptimizationState, bonus::Int=0) end end if !inlineable - inlineable = inline_worthy(me.src.code, me.src, me.sptypes, me.slottypes, me.params, cost_threshold + bonus) + inlineable = inline_worthy(me.src.code, me.src, me.sptypes, me.slottypes, params, cost_threshold + bonus) end return inlineable end @@ -168,7 +170,7 @@ function stmt_affects_purity(@nospecialize(stmt), ir) end # run the optimization work -function optimize(opt::OptimizationState, @nospecialize(result)) +function optimize(opt::OptimizationState, params::OptimizationParams, @nospecialize(result)) def = opt.linfo.def nargs = Int(opt.nargs) - 1 @timeit "optimizer" ir = run_passes(opt.src, nargs, opt) @@ -247,13 +249,13 @@ function optimize(opt::OptimizationState, @nospecialize(result)) else bonus = 0 if result ⊑ Tuple && !isbitstype(widenconst(result)) - bonus = opt.params.inline_tupleret_bonus + bonus = params.inline_tupleret_bonus end if opt.src.inlineable # For functions declared @inline, increase the cost threshold 20x - bonus += opt.params.inline_cost_threshold*19 + bonus += params.inline_cost_threshold*19 end - opt.src.inlineable = isinlineable(def, opt, bonus) + opt.src.inlineable = isinlineable(def, opt, params, bonus) end end nothing @@ -282,7 +284,7 @@ plus_saturate(x::Int, y::Int) = max(x, y, x+y) # known return type isknowntype(@nospecialize T) = (T === Union{}) || isa(T, Const) || isconcretetype(widenconst(T)) -function statement_cost(ex::Expr, line::Int, src::CodeInfo, sptypes::Vector{Any}, slottypes::Vector{Any}, params::Params) +function statement_cost(ex::Expr, line::Int, src::CodeInfo, sptypes::Vector{Any}, slottypes::Vector{Any}, params::OptimizationParams) head = ex.head if is_meta_expr_head(head) return 0 @@ -372,7 +374,7 @@ function statement_cost(ex::Expr, line::Int, src::CodeInfo, sptypes::Vector{Any} end function inline_worthy(body::Array{Any,1}, src::CodeInfo, sptypes::Vector{Any}, slottypes::Vector{Any}, - params::Params, cost_threshold::Integer=params.inline_cost_threshold) + params::OptimizationParams, cost_threshold::Integer=params.inline_cost_threshold) bodycost::Int = 0 for line = 1:length(body) stmt = body[line] diff --git a/base/compiler/params.jl b/base/compiler/params.jl deleted file mode 100644 index 46c086210dbbd..0000000000000 --- a/base/compiler/params.jl +++ /dev/null @@ -1,72 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -struct Params - cache::Vector{InferenceResult} - world::UInt - global_cache::Bool - - # optimization - inlining::Bool - ipo_constant_propagation::Bool - aggressive_constant_propagation::Bool - inline_cost_threshold::Int # number of CPU cycles beyond which it's not worth inlining - inline_nonleaf_penalty::Int # penalty for dynamic dispatch - inline_tupleret_bonus::Int # extra willingness for non-isbits tuple return types - - # 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 - # obtaining poor type information. - # It is important for N to be >= the number of methods in the error() - # function, so we can still know that error() is always Bottom. - MAX_METHODS::Int - # the maximum number of union-tuples to swap / expand - # before computing the set of matching methods - MAX_UNION_SPLITTING::Int - # the maximum number of union-tuples to swap / expand - # when inferring a call to _apply - MAX_APPLY_UNION_ENUM::Int - - # parameters limiting large (tuple) types - TUPLE_COMPLEXITY_LIMIT_DEPTH::Int - - # when attempting to inlining _apply, abort the optimization if the tuple - # contains more than this many elements - MAX_TUPLE_SPLAT::Int - - # reasonable defaults - global function CustomParams(world::UInt, - ; - inlining::Bool = inlining_enabled(), - inline_cost_threshold::Int = DEFAULT_PARAMS.inline_cost_threshold, - inline_nonleaf_penalty::Int = DEFAULT_PARAMS.inline_nonleaf_penalty, - inline_tupleret_bonus::Int = DEFAULT_PARAMS.inline_tupleret_bonus, - ipo_constant_propagation::Bool = true, - aggressive_constant_propagation::Bool = false, - max_methods::Int = DEFAULT_PARAMS.MAX_METHODS, - tupletype_depth::Int = DEFAULT_PARAMS.TUPLE_COMPLEXITY_LIMIT_DEPTH, - tuple_splat::Int = DEFAULT_PARAMS.MAX_TUPLE_SPLAT, - union_splitting::Int = DEFAULT_PARAMS.MAX_UNION_SPLITTING, - apply_union_enum::Int = DEFAULT_PARAMS.MAX_APPLY_UNION_ENUM) - return new(Vector{InferenceResult}(), - world, false, - inlining, ipo_constant_propagation, aggressive_constant_propagation, - inline_cost_threshold, inline_nonleaf_penalty, inline_tupleret_bonus, - max_methods, union_splitting, apply_union_enum, tupletype_depth, - tuple_splat) - end - function Params(world::UInt) - world == typemax(UInt) && (world = get_world_counter()) # workaround for bad callers - @assert world <= get_world_counter() - inlining = inlining_enabled() - return new(Vector{InferenceResult}(), - world, true, - #=inlining, ipo_constant_propagation, aggressive_constant_propagation, inline_cost_threshold, inline_nonleaf_penalty,=# - inlining, true, false, 100, 1000, - #=inline_tupleret_bonus, max_methods, union_splitting, apply_union_enum=# - 400, 4, 4, 8, - #=tupletype_depth, tuple_splat=# - 3, 32) - end -end -const DEFAULT_PARAMS = Params(UInt(0)) diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 0c90b4d3e9c6d..7c42385f28a98 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -581,7 +581,7 @@ function spec_lambda(@nospecialize(atype), sv::OptimizationState, @nospecialize( min_valid = UInt[typemin(UInt)] max_valid = UInt[typemax(UInt)] if invoke_data === nothing - mi = ccall(:jl_get_spec_lambda, Any, (Any, UInt, Ptr{UInt}, Ptr{UInt}), atype, sv.params.world, min_valid, max_valid) + mi = ccall(:jl_get_spec_lambda, Any, (Any, UInt, Ptr{UInt}, Ptr{UInt}), atype, sv.world, min_valid, max_valid) else invoke_data = invoke_data::InvokeData atype <: invoke_data.types0 || return nothing @@ -817,7 +817,7 @@ function handle_single_case!(ir::IRCode, stmt::Expr, idx::Int, @nospecialize(cas nothing end -function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::Params) +function is_valid_type_for_apply_rewrite(@nospecialize(typ), params::OptimizationParams) if isa(typ, Const) && isa(typ.val, SimpleVector) length(typ.val) > params.MAX_TUPLE_SPLAT && return false for p in typ.val @@ -887,7 +887,7 @@ function call_sig(ir::IRCode, stmt::Expr) Signature(f, ft, atypes) end -function inline_apply!(ir::IRCode, idx::Int, sig::Signature, params::Params) +function inline_apply!(ir::IRCode, idx::Int, sig::Signature, params::OptimizationParams) stmt = ir.stmts[idx] while sig.f === Core._apply || sig.f === Core._apply_iterate arg_start = sig.f === Core._apply ? 2 : 3 @@ -943,7 +943,7 @@ end # Handles all analysis and inlining of intrinsics and builtins. In particular, # this method does not access the method table or otherwise process generic # functions. -function process_simple!(ir::IRCode, idx::Int, params::Params) +function process_simple!(ir::IRCode, idx::Int, params::OptimizationParams, world::UInt) stmt = ir.stmts[idx] stmt isa Expr || return nothing if stmt.head === :splatnew @@ -971,7 +971,7 @@ function process_simple!(ir::IRCode, idx::Int, params::Params) # Handle invoke invoke_data = nothing if sig.f === Core.invoke && length(sig.atypes) >= 3 - res = compute_invoke_data(sig.atypes, params) + res = compute_invoke_data(sig.atypes, world) res === nothing && return nothing (sig, invoke_data) = res elseif is_builtin(sig) @@ -996,7 +996,7 @@ function assemble_inline_todo!(ir::IRCode, sv::OptimizationState) # todo = (inline_idx, (isva, isinvoke, na), method, spvals, inline_linetable, inline_ir, lie) todo = Any[] for idx in 1:length(ir.stmts) - r = process_simple!(ir, idx, sv.params) + r = process_simple!(ir, idx, sv.params, sv.world) r === nothing && continue stmt = ir.stmts[idx] @@ -1019,7 +1019,7 @@ function assemble_inline_todo!(ir::IRCode, sv::OptimizationState) min_val = UInt[typemin(UInt)] max_val = UInt[typemax(UInt)] ms = _methods_by_ftype(sig.atype, sv.params.MAX_METHODS, - sv.params.world, min_val, max_val) + sv.world, min_val, max_val) return (ms, min_val[1], max_val[1]) end if meth === false || length(meth) == 0 @@ -1126,7 +1126,7 @@ function linear_inline_eligible(ir::IRCode) return true end -function compute_invoke_data(@nospecialize(atypes), params::Params) +function compute_invoke_data(@nospecialize(atypes), world::UInt) ft = widenconst(atypes[2]) if !isdispatchelem(ft) || has_free_typevars(ft) || (ft <: Builtin) # TODO: this can be rather aggressive at preventing inlining of closures @@ -1145,7 +1145,7 @@ function compute_invoke_data(@nospecialize(atypes), params::Params) min_valid = UInt[typemin(UInt)] max_valid = UInt[typemax(UInt)] invoke_entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), - invoke_types, params.world) # XXX: min_valid, max_valid + invoke_types, world) # XXX: min_valid, max_valid invoke_entry === nothing && return nothing invoke_data = InvokeData(invoke_entry::Core.TypeMapEntry, invoke_types, min_valid[1], max_valid[1]) atype0 = atypes[2] @@ -1163,7 +1163,7 @@ function ispuretopfunction(@nospecialize(f)) istopfunction(f, :promote_type) end -function early_inline_special_case(ir::IRCode, s::Signature, e::Expr, params::Params, +function early_inline_special_case(ir::IRCode, s::Signature, e::Expr, params::OptimizationParams, @nospecialize(etype)) f, ft, atypes = s.f, s.ft, s.atypes if (f === typeassert || ft ⊑ typeof(typeassert)) && length(atypes) == 3 @@ -1198,7 +1198,7 @@ function early_inline_special_case(ir::IRCode, s::Signature, e::Expr, params::Pa return nothing end -function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::Expr, params::Params) +function late_inline_special_case!(ir::IRCode, sig::Signature, idx::Int, stmt::Expr, params::OptimizationParams) typ = ir.types[idx] f, ft, atypes = sig.f, sig.ft, sig.atypes if params.inlining && length(atypes) == 3 && istopfunction(f, :!==) @@ -1302,7 +1302,7 @@ function find_inferred(mi::MethodInstance, @nospecialize(atypes), sv::Optimizati end end if haveconst || improvable_via_constant_propagation(rettype) - inf_result = cache_lookup(mi, atypes, sv.params.cache) # Union{Nothing, InferenceResult} + inf_result = cache_lookup(mi, atypes, get_inference_cache(sv.interp)) # Union{Nothing, InferenceResult} else inf_result = nothing end @@ -1318,7 +1318,7 @@ function find_inferred(mi::MethodInstance, @nospecialize(atypes), sv::Optimizati end end - linfo = inf_for_methodinstance(mi, sv.params.world) + linfo = inf_for_methodinstance(sv.interp, mi, sv.world) if linfo isa CodeInstance if invoke_api(linfo) == 2 # in this case function can be inlined to a constant diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 5f5cc8cdb714b..3e87db3265df5 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1153,21 +1153,21 @@ function apply_type_tfunc(@nospecialize(headtypetype), @nospecialize args...) end add_tfunc(apply_type, 1, INT_INF, apply_type_tfunc, 10) -function invoke_tfunc(@nospecialize(ft), @nospecialize(types), @nospecialize(argtype), sv::InferenceState) +function invoke_tfunc(interp::AbstractInterpreter, @nospecialize(ft), @nospecialize(types), @nospecialize(argtype), sv::InferenceState) argtype = typeintersect(types, argtype) argtype === Bottom && return Bottom argtype isa DataType || return Any # other cases are not implemented below isdispatchelem(ft) || return Any # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below types = rewrap_unionall(Tuple{ft, unwrap_unionall(types).parameters...}, types) argtype = Tuple{ft, argtype.parameters...} - entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), types, sv.params.world) + entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), types, get_world_counter(interp)) if entry === nothing return Any end # XXX: update_valid_age!(min_valid[1], max_valid[1], sv) meth = entry.func (ti, env) = ccall(:jl_type_intersection_with_env, Any, (Any, Any), argtype, meth.sig)::SimpleVector - rt, edge = typeinf_edge(meth::Method, ti, env, sv) + rt, edge = typeinf_edge(interp, meth::Method, ti, env, sv) edge !== nothing && add_backedge!(edge::MethodInstance, sv) return rt end @@ -1304,8 +1304,8 @@ function builtin_nothrow(@nospecialize(f), argtypes::Array{Any, 1}, @nospecializ return _builtin_nothrow(f, argtypes, rt) end -function builtin_tfunction(@nospecialize(f), argtypes::Array{Any,1}, - sv::Union{InferenceState,Nothing}, params::Params = sv.params) +function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Array{Any,1}, + sv::Union{InferenceState,Nothing}) isva = !isempty(argtypes) && isvarargtype(argtypes[end]) if f === tuple return tuple_tfunc(argtypes) @@ -1357,7 +1357,7 @@ function builtin_tfunction(@nospecialize(f), argtypes::Array{Any,1}, sigty = nothing end if isa(sigty, Type) && !has_free_typevars(sigty) && sigty <: Tuple - return invoke_tfunc(ft, sigty, argtypes_to_type(argtypes[3:end]), sv) + return invoke_tfunc(interp, ft, sigty, argtypes_to_type(argtypes[3:end]), sv) end end return Any @@ -1475,7 +1475,7 @@ end # TODO: this function is a very buggy and poor model of the return_type function # since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type, # while this assumes that it is an absolutely precise and accurate and exact model of both -function return_type_tfunc(argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) +function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState) if length(argtypes) == 3 tt = argtypes[3] if isa(tt, Const) || (isType(tt) && !has_free_typevars(tt)) @@ -1488,7 +1488,7 @@ function return_type_tfunc(argtypes::Vector{Any}, vtypes::VarTable, sv::Inferenc if contains_is(argtypes_vec, Union{}) return Const(Union{}) end - rt = abstract_call(nothing, argtypes_vec, vtypes, sv, -1) + rt = abstract_call(interp, nothing, argtypes_vec, vtypes, sv, -1) if isa(rt, Const) # output was computed to be constant return Const(typeof(rt.val)) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 5091ea08582cf..0e367bb3f6449 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -1,15 +1,15 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # build (and start inferring) the inference frame for the linfo -function typeinf(result::InferenceResult, cached::Bool, params::Params) - frame = InferenceState(result, cached, params) +function typeinf(interp::AbstractInterpreter, result::InferenceResult, cached::Bool) + frame = InferenceState(result, cached, interp) frame === nothing && return false cached && (result.linfo.inInference = true) - return typeinf(frame) + return typeinf(interp, frame) end -function typeinf(frame::InferenceState) - typeinf_nocycle(frame) || return false # frame is now part of a higher cycle +function typeinf(interp::AbstractInterpreter, frame::InferenceState) + typeinf_nocycle(interp, frame) || return false # frame is now part of a higher cycle # with no active ip's, frame is done frames = frame.callers_in_cycle isempty(frames) && push!(frames, frame) @@ -18,7 +18,7 @@ function typeinf(frame::InferenceState) caller.dont_work_on_me = true end for caller in frames - finish(caller) + finish(caller, interp) end # collect results for the new expanded frame results = InferenceResult[ frames[i].result for i in 1:length(frames) ] @@ -30,8 +30,8 @@ function typeinf(frame::InferenceState) for caller in results opt = caller.src if opt isa OptimizationState - optimize(opt, caller.result) - finish(opt.src) + optimize(opt, OptimizationParams(interp), caller.result) + finish(opt.src, interp) # finish updating the result struct validate_code_in_debug_mode(opt.linfo, opt.src, "optimized") if opt.const_api @@ -64,7 +64,7 @@ function typeinf(frame::InferenceState) caller.src.min_world = min_valid caller.src.max_world = max_valid if cached - cache_result(caller.result, min_valid, max_valid) + cache_result(interp, caller.result, min_valid, max_valid) end if max_valid == typemax(UInt) # if we aren't cached, we don't need this edge @@ -81,14 +81,14 @@ end # inference completed on `me` # update the MethodInstance and notify the edges -function cache_result(result::InferenceResult, min_valid::UInt, max_valid::UInt) +function cache_result(interp::AbstractInterpreter, result::InferenceResult, min_valid::UInt, max_valid::UInt) def = result.linfo.def toplevel = !isa(result.linfo.def, Method) # check if the existing linfo metadata is also sufficient to describe the current inference result # to decide if it is worth caching this already_inferred = !result.linfo.inInference - if inf_for_methodinstance(result.linfo, min_valid, max_valid) isa CodeInstance + if inf_for_methodinstance(interp, result.linfo, min_valid, max_valid) isa CodeInstance already_inferred = true end @@ -136,7 +136,7 @@ function cache_result(result::InferenceResult, min_valid::UInt, max_valid::UInt) nothing end -function finish(me::InferenceState) +function finish(me::InferenceState, interp::AbstractInterpreter) # prepare to run optimization passes on fulltree if me.limited && me.cached && me.parent !== nothing # a top parent will be cached still, but not this intermediate work @@ -151,7 +151,7 @@ function finish(me::InferenceState) if run_optimizer # construct the optimizer for later use, if we're building this IR to cache it # (otherwise, we'll run the optimization passes later, outside of inference) - opt = OptimizationState(me) + opt = OptimizationState(me, OptimizationParams(interp), interp) me.result.src = opt end end @@ -159,7 +159,7 @@ function finish(me::InferenceState) nothing end -function finish(src::CodeInfo) +function finish(src::CodeInfo, interp::AbstractInterpreter) # convert all type information into the form consumed by the cache for inlining and code-generation widen_all_consts!(src) src.inferred = true @@ -450,9 +450,9 @@ function resolve_call_cycle!(linfo::MethodInstance, parent::InferenceState) end # compute (and cache) an inferred AST and return the current best estimate of the result type -function typeinf_edge(method::Method, @nospecialize(atypes), sparams::SimpleVector, caller::InferenceState) +function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize(atypes), sparams::SimpleVector, caller::InferenceState) mi = specialize_method(method, atypes, sparams)::MethodInstance - code = inf_for_methodinstance(mi, caller.params.world) + code = inf_for_methodinstance(interp, mi, get_world_counter(interp)) if code isa CodeInstance # return existing rettype if the code is already inferred update_valid_age!(min_world(code), max_world(code), caller) if isdefined(code, :rettype_const) @@ -472,7 +472,7 @@ function typeinf_edge(method::Method, @nospecialize(atypes), sparams::SimpleVect # completely new mi.inInference = true result = InferenceResult(mi) - frame = InferenceState(result, #=cached=#true, caller.params) # always use the cache for edge targets + frame = InferenceState(result, #=cached=#true, interp) # always use the cache for edge targets if frame === nothing # can't get the source for this, so we know nothing mi.inInference = false @@ -481,7 +481,7 @@ function typeinf_edge(method::Method, @nospecialize(atypes), sparams::SimpleVect if caller.cached || caller.limited # don't involve uncached functions in cycle resolution frame.parent = caller end - typeinf(frame) + typeinf(interp, frame) update_valid_age!(frame, caller) return widenconst_bestguess(frame.bestguess), frame.inferred ? mi : nothing elseif frame === true @@ -502,15 +502,16 @@ end #### entry points for inferring a MethodInstance given a type signature #### # compute an inferred AST and return type -function typeinf_code(method::Method, @nospecialize(atypes), sparams::SimpleVector, run_optimizer::Bool, params::Params) +function typeinf_code(interp::AbstractInterpreter, method::Method, @nospecialize(atypes), sparams::SimpleVector, run_optimizer::Bool) mi = specialize_method(method, atypes, sparams)::MethodInstance ccall(:jl_typeinf_begin, Cvoid, ()) result = InferenceResult(mi) - frame = InferenceState(result, false, params) + frame = InferenceState(result, false, interp) frame === nothing && return (nothing, Any) - if typeinf(frame) && run_optimizer - opt = OptimizationState(frame) - optimize(opt, result.result) + if typeinf(interp, frame) && run_optimizer + opt_params = OptimizationParams(interp) + opt = OptimizationState(frame, opt_params, interp) + optimize(opt, opt_params, result.result) opt.src.inferred = true end ccall(:jl_typeinf_end, Cvoid, ()) @@ -519,11 +520,11 @@ function typeinf_code(method::Method, @nospecialize(atypes), sparams::SimpleVect end # compute (and cache) an inferred AST and return type -function typeinf_ext(mi::MethodInstance, params::Params) +function typeinf_ext(interp::AbstractInterpreter, mi::MethodInstance) method = mi.def::Method for i = 1:2 # test-and-lock-and-test i == 2 && ccall(:jl_typeinf_begin, Cvoid, ()) - code = inf_for_methodinstance(mi, params.world) + code = inf_for_methodinstance(interp, mi, get_world_counter(interp)) if code isa CodeInstance # see if this code already exists in the cache inf = code.inferred @@ -565,23 +566,23 @@ function typeinf_ext(mi::MethodInstance, params::Params) end end mi.inInference = true - frame = InferenceState(InferenceResult(mi), #=cached=#true, params) + frame = InferenceState(InferenceResult(mi), #=cached=#true, interp) frame === nothing && return nothing - typeinf(frame) + typeinf(interp, frame) ccall(:jl_typeinf_end, Cvoid, ()) frame.src.inferred || return nothing return frame.src end # compute (and cache) an inferred AST and return the inferred return type -function typeinf_type(method::Method, @nospecialize(atypes), sparams::SimpleVector, params::Params) +function typeinf_type(interp::AbstractInterpreter, method::Method, @nospecialize(atypes), sparams::SimpleVector) if contains_is(unwrap_unionall(atypes).parameters, Union{}) return Union{} # don't ask: it does weird and unnecessary things, if it occurs during bootstrap end mi = specialize_method(method, atypes, sparams)::MethodInstance for i = 1:2 # test-and-lock-and-test i == 2 && ccall(:jl_typeinf_begin, Cvoid, ()) - code = inf_for_methodinstance(mi, params.world) + code = inf_for_methodinstance(interp, mi, get_world_counter(interp)) if code isa CodeInstance # see if this rettype already exists in the cache i == 2 && ccall(:jl_typeinf_end, Cvoid, ()) @@ -589,16 +590,18 @@ function typeinf_type(method::Method, @nospecialize(atypes), sparams::SimpleVect end end frame = InferenceResult(mi) - typeinf(frame, true, params) + typeinf(interp, frame, true) ccall(:jl_typeinf_end, Cvoid, ()) frame.result isa InferenceState && return nothing return widenconst(frame.result) end -@timeit function typeinf_ext(linfo::MethodInstance, world::UInt) +# This is a bridge for the C code calling `jl_typeinf_func()` +typeinf_ext_toplevel(mi::MethodInstance, world::UInt) = typeinf_ext_toplevel(NativeInterpreter(world), mi) +function typeinf_ext_toplevel(interp::AbstractInterpreter, linfo::MethodInstance) if isa(linfo.def, Method) # method lambda - infer this specialization via the method cache - src = typeinf_ext(linfo, Params(world)) + src = typeinf_ext(interp, linfo) else src = linfo.uninferred::CodeInfo if !src.inferred @@ -606,8 +609,8 @@ end ccall(:jl_typeinf_begin, Cvoid, ()) if !src.inferred result = InferenceResult(linfo) - frame = InferenceState(result, src, #=cached=#true, Params(world)) - typeinf(frame) + frame = InferenceState(result, src, #=cached=#true, interp) + typeinf(interp, frame) @assert frame.inferred # TODO: deal with this better src = frame.src end @@ -623,19 +626,20 @@ function return_type(@nospecialize(f), @nospecialize(t)) return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), Any[_return_type, f, t, world], 4) end -function _return_type(@nospecialize(f), @nospecialize(t), world) - params = Params(world) +_return_type(@nospecialize(f), @nospecialize(t), world) = _return_type(NativeInterpreter(world), f, t) + +function _return_type(interp::AbstractInterpreter, @nospecialize(f), @nospecialize(t)) rt = Union{} if isa(f, Builtin) - rt = builtin_tfunction(f, Any[t.parameters...], nothing, params) + rt = builtin_tfunction(interp, f, Any[t.parameters...], nothing) if isa(rt, TypeVar) rt = rt.ub else rt = widenconst(rt) end else - for m in _methods(f, t, -1, params.world) - ty = typeinf_type(m[3], m[1], m[2], params) + for m in _methods(f, t, -1, get_world_counter(interp)) + ty = typeinf_type(interp, m[3], m[1], m[2]) ty === nothing && return Any rt = tmerge(rt, ty) rt === Any && break diff --git a/base/compiler/types.jl b/base/compiler/types.jl new file mode 100644 index 0000000000000..ae73c523d22ed --- /dev/null +++ b/base/compiler/types.jl @@ -0,0 +1,177 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + AbstractInterpreter + +An abstract base class that allows multiple dispatch to determine the method of +executing Julia code. The native Julia LLVM pipeline is enabled by using the +`TypeInference` concrete instantiatoin of this abstract class, others can be +swapped in as long as they follow the AbstractInterpreter API. + +All AbstractInterpreters are expected to provide at least the following methods: + +- InferenceParams(interp) - return an `InferenceParams` instance +- OptimizationParams(interp) - return an `OptimizationParams` instance +- get_world_counter(interp) - return the world age for this interpreter +- get_inference_cache(interp) - return the runtime inference cache +""" +abstract type AbstractInterpreter; end + + +""" + InferenceResult + +A type that represents the result of running type inference on a chunk of code. +""" +mutable struct InferenceResult + linfo::MethodInstance + argtypes::Vector{Any} + overridden_by_const::BitVector + result # ::Type, or InferenceState if WIP + src #::Union{CodeInfo, OptimizationState, Nothing} # if inferred copy is available + function InferenceResult(linfo::MethodInstance, given_argtypes = nothing) + argtypes, overridden_by_const = matching_cache_argtypes(linfo, given_argtypes) + return new(linfo, argtypes, overridden_by_const, Any, nothing) + end +end + + +""" + OptimizationParams + +Parameters that control optimizer operation. +""" +struct OptimizationParams + inlining::Bool # whether inlining is enabled + inline_cost_threshold::Int # number of CPU cycles beyond which it's not worth inlining + inline_nonleaf_penalty::Int # penalty for dynamic dispatch + inline_tupleret_bonus::Int # extra willingness for non-isbits tuple return types + + # Duplicating for now because optimizer inlining requires it. + # Keno assures me this will be removed in the near future + MAX_METHODS::Int + MAX_TUPLE_SPLAT::Int + MAX_UNION_SPLITTING::Int + + function OptimizationParams(; + inlining::Bool = inlining_enabled(), + inline_cost_threshold::Int = 100, + inline_nonleaf_penalty::Int = 1000, + inline_tupleret_bonus::Int = 400, + max_methods::Int = 4, + tuple_splat::Int = 32, + union_splitting::Int = 4, + ) + return new( + inlining, + inline_cost_threshold, + inline_nonleaf_penalty, + inline_tupleret_bonus, + max_methods, + tuple_splat, + union_splitting, + ) + end +end + +""" + InferenceParams + +Parameters that control type inference operation. +""" +struct InferenceParams + ipo_constant_propagation::Bool + aggressive_constant_propagation::Bool + + # 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 + # obtaining poor type information. + # It is important for N to be >= the number of methods in the error() + # function, so we can still know that error() is always Bottom. + MAX_METHODS::Int + # the maximum number of union-tuples to swap / expand + # before computing the set of matching methods + MAX_UNION_SPLITTING::Int + # the maximum number of union-tuples to swap / expand + # when inferring a call to _apply + MAX_APPLY_UNION_ENUM::Int + + # parameters limiting large (tuple) types + TUPLE_COMPLEXITY_LIMIT_DEPTH::Int + + # when attempting to inlining _apply, abort the optimization if the tuple + # contains more than this many elements + MAX_TUPLE_SPLAT::Int + + function InferenceParams(; + ipo_constant_propagation::Bool = true, + aggressive_constant_propagation::Bool = false, + max_methods::Int = 4, + union_splitting::Int = 4, + apply_union_enum::Int = 8, + tupletype_depth::Int = 3, + tuple_splat::Int = 32, + ) + return new( + ipo_constant_propagation, + aggressive_constant_propagation, + max_methods, + union_splitting, + apply_union_enum, + tupletype_depth, + tuple_splat, + ) + end +end + +""" + NativeInterpreter + +This represents Julia's native type inference algorithm and codegen backend. +It contains many parameters used by the compilation pipeline. +""" +struct NativeInterpreter <: AbstractInterpreter + # Cache of inference results for this particular interpreter + cache::Vector{InferenceResult} + # The world age we're working inside of + world::UInt + + # Parameters for inference and optimization + inf_params::InferenceParams + opt_params::OptimizationParams + + function NativeInterpreter(world::UInt = get_world_counter(); + inf_params = InferenceParams(), + opt_params = OptimizationParams(), + ) + # Sometimes the caller is lazy and passes typemax(UInt). + # we cap it to the current world age + if world == typemax(UInt) + world = get_world_counter() + end + + # If they didn't pass typemax(UInt) but passed something more subtly + # incorrect, fail out loudly. + @assert world <= get_world_counter() + + + return new( + # Initially empty cache + Vector{InferenceResult}(), + + # world age counter + world, + + # parameters for inference and optimization + inf_params, + opt_params, + ) + end +end + +# Quickly and easily satisfy the AbstractInterpreter API contract +InferenceParams(ni::NativeInterpreter) = ni.inf_params +OptimizationParams(ni::NativeInterpreter) = ni.opt_params +get_world_counter(ni::NativeInterpreter) = ni.world +get_inference_cache(ni::NativeInterpreter) = ni.cache diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 84bfd07de5408..0fa4f776de681 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -118,7 +118,7 @@ function retrieve_code_info(linfo::MethodInstance) end end -function inf_for_methodinstance(mi::MethodInstance, min_world::UInt, max_world::UInt=min_world) +function inf_for_methodinstance(interp::AbstractInterpreter, mi::MethodInstance, min_world::UInt, max_world::UInt=min_world) return ccall(:jl_rettype_inferred, Any, (Any, UInt, UInt), mi, min_world, max_world)::Union{Nothing, CodeInstance} end diff --git a/base/reflection.jl b/base/reflection.jl index 585397dac97a2..3b3e745559593 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1077,7 +1077,7 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple); optimize=true, debuginfo::Symbol=:default, world = get_world_counter(), - params = Core.Compiler.Params(world)) + interp = Core.Compiler.NativeInterpreter(world)) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) @@ -1094,7 +1094,7 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple); asts = [] for x in _methods(f, types, -1, world) meth = func_for_method_checked(x[3], types, x[2]) - (code, ty) = Core.Compiler.typeinf_code(meth, x[1], x[2], optimize, params) + (code, ty) = Core.Compiler.typeinf_code(interp, meth, x[1], x[2], optimize) code === nothing && error("inference not successful") # inference disabled? debuginfo === :none && remove_linenums!(code) push!(asts, code => ty) @@ -1102,7 +1102,7 @@ function code_typed(@nospecialize(f), @nospecialize(types=Tuple); return asts end -function return_types(@nospecialize(f), @nospecialize(types=Tuple)) +function return_types(@nospecialize(f), @nospecialize(types=Tuple), interp=Core.Compiler.NativeInterpreter()) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) @@ -1110,10 +1110,9 @@ function return_types(@nospecialize(f), @nospecialize(types=Tuple)) types = to_tuple_type(types) rt = [] world = get_world_counter() - params = Core.Compiler.Params(world) for x in _methods(f, types, -1, world) meth = func_for_method_checked(x[3], types, x[2]) - ty = Core.Compiler.typeinf_type(meth, x[1], x[2], params) + ty = Core.Compiler.typeinf_type(interp, meth, x[1], x[2]) ty === nothing && error("inference not successful") # inference disabled? push!(rt, ty) end diff --git a/base/show.jl b/base/show.jl index 77adf0454894f..22b9cb41e42cb 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1898,6 +1898,20 @@ function show(io::IO, src::CodeInfo; debuginfo::Symbol=:source) print(io, ")") end +function show(io::IO, inferred::Core.Compiler.InferenceResult) + tt = inferred.linfo.specTypes.parameters[2:end] + tts = join(["::$(t)" for t in tt], ", ") + rettype = inferred.result + if isa(rettype, Core.Compiler.InferenceState) + rettype = rettype.bestguess + end + print(io, "$(inferred.linfo.def.name)($(tts)) => $(rettype)") +end + +function show(io::IO, ::Core.Compiler.NativeInterpreter) + print(io, "Core.Compiler.NativeInterpreter") +end + function dump(io::IOContext, x::SimpleVector, n::Int, indent) if isempty(x) diff --git a/doc/src/devdocs/inference.md b/doc/src/devdocs/inference.md index 8a7ee4d1fcf15..6c8383d19c5d1 100644 --- a/doc/src/devdocs/inference.md +++ b/doc/src/devdocs/inference.md @@ -34,10 +34,12 @@ mths = methods(convert, atypes) # worth checking that there is only one m = first(mths) # Create variables needed to call `typeinf_code` -params = Core.Compiler.Params(Base.get_world_counter()) # parameter is the world age, -sparams = Core.svec(Int64) # this particular method has a type-parameter -optimize = true # run all inference optimizations -Core.Compiler.typeinf_code(m, atypes, sparams, optimize, params) +interp = Core.Compiler.NativeInterpreter() +sparams = Core.svec() # this particular method doesn't have type-parameters +optimize = true # run all inference optimizations +cached = false # force inference to happen (do not use cached results) +types = Tuple{typeof(convert), atypes.parameters...} # Tuple{typeof(convert), Type{Int}, UInt} +Core.Compiler.typeinf_code(interp, types, sparams, optimize, cached) ``` If your debugging adventures require a `MethodInstance`, you can look it up by @@ -103,10 +105,11 @@ where `f` is your function and `tt` is the Tuple-type of the arguments: f = fill tt = Tuple{Float64, Tuple{Int,Int}} # Create the objects we need to interact with the compiler -params = Core.Compiler.Params(typemax(UInt)) +opt_params = Core.Compiler.OptimizationParams() mi = Base.method_instances(f, tt)[1] ci = code_typed(f, tt)[1][1] -opt = Core.Compiler.OptimizationState(mi, params) +interp = Core.Compiler.NativeInterpreter() +opt = Core.Compiler.OptimizationState(mi, opt_params, interp) # Calculate cost of each statement cost(stmt::Expr) = Core.Compiler.statement_cost(stmt, -1, ci, opt.sptypes, opt.slottypes, opt.params) cost(stmt) = 0 diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index aa5906c8d8d79..a16565bba3751 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -386,8 +386,8 @@ function get_type_call(expr::Expr) length(mt) == 1 || return (Any, false) m = first(mt) # Typeinference - params = Core.Compiler.Params(world) - return_type = Core.Compiler.typeinf_type(m[3], m[1], m[2], params) + interp = Core.Compiler.NativeInterpreter() + return_type = Core.Compiler.typeinf_type(interp, m[3], m[1], m[2]) return_type === nothing && return (Any, false) return (return_type, true) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index fd2e6138e561d..fec496f321b5c 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1144,7 +1144,8 @@ function get_linfo(@nospecialize(f), @nospecialize(t)) end function test_const_return(@nospecialize(f), @nospecialize(t), @nospecialize(val)) - linfo = Core.Compiler.inf_for_methodinstance(get_linfo(f, t), Core.Compiler.get_world_counter())::Core.CodeInstance + interp = Core.Compiler.NativeInterpreter() + linfo = Core.Compiler.inf_for_methodinstance(interp, get_linfo(f, t), Core.Compiler.get_world_counter())::Core.CodeInstance # If coverage is not enabled, make the check strict by requiring constant ABI # Otherwise, check the typed AST to make sure we return a constant. if Base.JLOptions().code_coverage == 0 @@ -1444,7 +1445,8 @@ gg13183(x::X...) where {X} = (_false13183 ? gg13183(x, x) : 0) # test the external OptimizationState constructor let linfo = get_linfo(Base.convert, Tuple{Type{Int64}, Int32}), world = UInt(23) # some small-numbered world that should be valid - opt = Core.Compiler.OptimizationState(linfo, Core.Compiler.Params(world)) + interp = Core.Compiler.NativeInterpreter() + opt = Core.Compiler.OptimizationState(linfo, Core.Compiler.OptimizationParams(interp), interp) # make sure the state of the properties look reasonable @test opt.src !== linfo.def.source @test length(opt.src.slotflags) == linfo.def.nargs <= length(opt.src.slotnames) diff --git a/test/reflection.jl b/test/reflection.jl index f79bc25a42118..4b77f2e2ac058 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -513,14 +513,15 @@ let code_typed(f18888, Tuple{}; optimize=false) @test !isempty(m.specializations) # uncached, but creates the specializations entry mi = Core.Compiler.specialize_method(m, Tuple{ft}, Core.svec()) - @test Core.Compiler.inf_for_methodinstance(mi, world) === nothing + interp = Core.Compiler.NativeInterpreter(world) + @test Core.Compiler.inf_for_methodinstance(interp, mi, world) === nothing @test !isdefined(mi, :cache) code_typed(f18888, Tuple{}; optimize=true) @test !isdefined(mi, :cache) Base.return_types(f18888, Tuple{}) - @test Core.Compiler.inf_for_methodinstance(mi, world) === mi.cache + @test Core.Compiler.inf_for_methodinstance(interp, mi, world) === mi.cache @test mi.cache isa Core.CodeInstance @test !isdefined(mi.cache, :next) end