From 20c6a595181374195bd5b0be8f5742523eb35b5a Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 6 Nov 2017 18:21:38 -0500 Subject: [PATCH] wip --- base/inference.jl | 223 ++++++++++++++++------------------------------ 1 file changed, 76 insertions(+), 147 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 6e517af8d0a35d..a3c33cd50e0cee 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -332,9 +332,14 @@ mutable struct InferenceState false, false, optimize, cached, false, false, false) result.result = frame cached && push!(params.cache, result) + global cache_max_len + length(params.cache) > cache_max_len && (cache_max_len = length(params.cache)) return frame end end +cache_max_len = 0 +cache_nlookups = 0 +cache_sizeof = 0 function InferenceState(linfo::MethodInstance, optimize::Bool, cached::Bool, params::InferenceParams) @@ -1969,6 +1974,46 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp return rettype end +function cache_lookup(code::MethodInstance, argtypes::Vector{Any}, cache::Vector{InferenceResult}) + method = code.def::Method + nargs::Int = method.nargs + method.isva && (nargs -= 1) + global cache_nlookups += 1 + global cache_sizeof += length(cache) + for cache_code in cache + # try to search cache first + cache_args = cache_code.args + if cache_code.linfo === code && length(cache_args) >= nargs + cache_match = true + # verify that the trailing args (va) aren't Const + for i in (nargs + 1):length(cache_args) + if isa(cache_args[i], Const) + cache_match = false + break + end + end + cache_match || continue + for i in 1:nargs + a = argtypes[i] + ca = cache_args[i] + # verify that all Const argument types match between the call and cache + if (isa(a, Const) || isa(ca, Const)) && !(a === ca) + cache_match = false + break + end + end + cache_match || continue + if debug_enabled && (length(argtypes) != length(cache_code.args) || !all(i -> argtypes[i] === cache_code.args[i], 1:nargs)) + println(argtypes) + println(cache_code) + end + return cache_code + end + end + return nothing +end +debug_enabled = false + function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState) method = match[3]::Method nargs::Int = method.nargs @@ -1991,34 +2036,7 @@ function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::Simp code = code_for_method(method, sig, sparams, sv.params.world) code === nothing && return Any code = code::MethodInstance - inf_result = nothing - for cache_code in sv.params.cache - # try to search cache first - cache_args = cache_code.args - if cache_code.linfo === code && length(cache_args) >= nargs - cache_match = true - for i in (nargs + 1):length(cache_args) - if isa(cache_args[i], Const) - cache_match = false - break - end - end - if cache_match - for i in 1:nargs - a = argtypes[i] - ca = cache_args[i] - if !(isa(a, Const) && isa(ca, Const) && ca.val === a.val) - cache_match = false - break - end - end - if cache_match - inf_result = cache_code - break - end - end - end - end + inf_result = cache_lookup(code, argtypes, sv.params.cache) if inf_result === nothing inf_result = InferenceResult(code) atypes = get_argtypes(inf_result) @@ -2028,7 +2046,7 @@ function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::Simp atypes[i] = a # inject Const argtypes into inference end end - frame = InferenceState(inf_result, #=optimize=#false, #=cache=#false, sv.params) + frame = InferenceState(inf_result, #=optimize=#true, #=cache=#false, sv.params) frame.limited = true frame.parent = sv push!(sv.params.cache, inf_result) @@ -2548,37 +2566,6 @@ function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argt return rty end - if la>2 && argtypes[3] ⊑ Int - at2 = widenconst(argtypes[2]) - if la==3 && at2 <: SimpleVector && istopfunction(tm, f, :getindex) - if isa(argtypes[2], Const) && isa(argtypes[3], Const) - svecval = argtypes[2].val - idx = argtypes[3].val - if isa(idx, Int) && 1 <= idx <= length(svecval) && - isassigned(svecval, idx) - return Const(getindex(svecval, idx)) - end - end - elseif (at2 <: Tuple || - (isa(at2, DataType) && (at2::DataType).name === Pair_name())) - # allow tuple indexing functions to take advantage of constant - # index arguments. - if istopfunction(tm, f, :getindex) && la==3 - return getfield_tfunc(argtypes[2], argtypes[3]) - elseif istopfunction(tm, f, :next) && la==3 - t1 = widenconst(getfield_tfunc(argtypes[2], argtypes[3])) - return t1===Bottom ? Bottom : Tuple{t1, Int} - elseif istopfunction(tm, f, :indexed_next) && la==4 - t1 = widenconst(getfield_tfunc(argtypes[2], argtypes[3])) - return t1===Bottom ? Bottom : Tuple{t1, Int} - end - end - elseif la==2 && argtypes[2] ⊑ SimpleVector && istopfunction(tm, f, :length) - if isa(argtypes[2], Const) - return Const(length(argtypes[2].val)) - end - end - atype = argtypes_to_type(argtypes) t = pure_eval_call(f, argtypes, atype, sv) t !== false && return t @@ -4742,29 +4729,8 @@ function inlineable(@nospecialize(f), @nospecialize(ft), e::Expr, atypes::Vector isa(methsp[i], TypeVar) && return NF end - # some gf have special tfunc, meaning they wouldn't have been inferred yet - # check the same conditions from abstract_call to detect this case - force_infer = false - if !isdefined(method, :generator) - if method.module == _topmod(method.module) || (isdefined(Main, :Base) && method.module == Main.Base) - la = length(atypes) - if (la==3 && (method.name == :getindex || method.name == :next)) || - (la==4 && method.name == :indexed_next) - if atypes[3] ⊑ Int - at2 = widenconst(atypes[2]) - if (at2 <: Tuple || at2 <: SimpleVector || - (isa(at2, DataType) && (at2::DataType).name === Pair_name())) - force_infer = true - end - end - elseif la == 2 && method.name == :length && atypes[2] ⊑ SimpleVector - force_infer = true - end - end - end - # see if the method has been previously inferred (and cached) - linfo = code_for_method(method, metharg, methsp, sv.params.world, !force_infer) # Union{Void, MethodInstance} + linfo = code_for_method(method, metharg, methsp, sv.params.world, true) # Union{Void, MethodInstance} isa(linfo, MethodInstance) || return invoke_NF(argexprs0, e.typ, atypes0, sv, atype_unlimited, invoke_data) linfo = linfo::MethodInstance @@ -4774,60 +4740,30 @@ function inlineable(@nospecialize(f), @nospecialize(ft), e::Expr, atypes::Vector return inline_as_constant(linfo.inferred_const, argexprs, sv, invoke_data) end - # see if the method has a current InferenceState frame - # or existing inferred code info - frame = nothing # Union{Void, InferenceState} - inferred = nothing # Union{Void, CodeInfo} - if force_infer && la > 2 && isa(atypes[3], Const) - # Since we inferred this with the information that atypes[3]::Const, - # must inline with that same information. - # We do that by overriding the argument type, - # while ensuring we don't cache that information - # This isn't particularly important for `getindex`, - # as we'll be able to fix that up at the end of inlinable when we verify the return type. - # But `next` and `indexed_next` make tuples which would end up burying some of that information in the AST - # where we can't easily correct it afterwards. - inf_result = InferenceResult(linfo) - get_argtypes(inf_result)[3] = atypes[3] # inject Const into atypes[3] - frame = InferenceState(inf_result, #=optimize=#true, #=cache=#false, sv.params) - typeinf(frame) - else - if isdefined(linfo, :inferred) && linfo.inferred !== nothing - # use cache - inferred = linfo.inferred - elseif force_infer - # create inferred code on-demand - # but if we decided in the past not to try to infer this particular signature - # (due to signature coarsening in abstract_call_gf_by_type) - # don't infer it now, as attempting to force it now would be a bad idea (non terminating) - frame = typeinf_frame(linfo, #=optimize=#true, #=cache=#true, sv.params) - end - end - - # compute the return value - if isa(frame, InferenceState) && !frame.src.inferred - frame = nothing - end - if isa(frame, InferenceState) - linfo = frame.linfo - inferred = frame.src - if frame.const_api # handle like jlcall_api == 2 - if frame.inferred || !frame.cached - add_backedge!(linfo, sv) - else - add_backedge!(frame, sv) - end - if isa(frame.bestguess, Const) - inferred_const = (frame.bestguess::Const).val - else - @assert isconstType(frame.bestguess) - inferred_const = frame.bestguess.parameters[1] - end - return inline_as_constant(inferred_const, argexprs, sv, invoke_data) - end - rettype = widenconst(frame.bestguess) - else + # see if the method has a InferenceResult in the current cache + # or an existing inferred code info store in `.inferred` + inf_result = cache_lookup(linfo, atypes, sv.params.cache) # Union{Void, InferenceResult} + if isa(inf_result, InferenceResult) && isa(inf_result.src, CodeInfo) + linfo = inf_result.linfo + result = inf_result.result + ## TODO: enable for pure functions + #if isa(result, Const) + # inferred_const = result.val + #elseif isconstType(result) + # inferred_const = result.parameters[1] + #end + #if @isdefined inferred_const + # add_backedge!(linfo, sv) + # return inline_as_constant(inferred_const, argexprs, sv, invoke_data) + #end + inferred = inf_result.src + rettype = widenconst(result) + elseif isdefined(linfo, :inferred) + inferred = linfo.inferred rettype = linfo.rettype + else + rettype = Any + inferred = nothing end # check that the code is inlineable @@ -4842,26 +4778,19 @@ function inlineable(@nospecialize(f), @nospecialize(ft), e::Expr, atypes::Vector invoke_data) end + # create the backedge + add_backedge!(linfo, sv) + + # prepare the code object for mutation if isa(inferred, CodeInfo) src = inferred ast = copy_exprargs(inferred.code) else - src = ccall(:jl_uncompress_ast, Any, (Any, Any), method, inferred)::CodeInfo + src = ccall(:jl_uncompress_ast, Any, (Any, Any), method, inferred::Vector{UInt8})::CodeInfo ast = src.code end ast = ast::Array{Any,1} - - # create the backedge - if isa(frame, InferenceState) && !frame.inferred && frame.cached - # in this case, the actual backedge linfo hasn't been computed - # yet, but will be when inference on the frame finishes - add_backedge!(frame, sv) - else - add_backedge!(linfo, sv) - end - nm = length(unwrap_unionall(metharg).parameters) - body = Expr(:block) body.args = ast