From 07914b7b30fbf83a5ab9c067b6e2141c619a9897 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 20 Sep 2016 00:09:57 -0400 Subject: [PATCH 1/2] fix llvm 3.3 build --- src/codegen.cpp | 2 +- src/dump.c | 1 + src/llvm-version.h | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 49b9e036b319e..a4985d23961c3 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4193,7 +4193,7 @@ static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_ #endif #ifdef USE_POLLY - if (!jl_has_meta(code, polly_sym) || jl_options.polly == JL_OPTIONS_POLLY_OFF) { + if (!jl_has_meta(stmts, polly_sym) || jl_options.polly == JL_OPTIONS_POLLY_OFF) { f->addFnAttr(polly::PollySkipFnAttr); } #endif diff --git a/src/dump.c b/src/dump.c index 4103657276cf4..ac17c2e050816 100644 --- a/src/dump.c +++ b/src/dump.c @@ -676,6 +676,7 @@ static void jl_serialize_module(jl_serializer_state *s, jl_module_t *m) static int is_ast_node(jl_value_t *v) { + // TODO: this accidentally copies QuoteNode(Expr(...)) and QuoteNode(svec(...)) return jl_is_symbol(v) || jl_is_slot(v) || jl_is_ssavalue(v) || jl_is_expr(v) || jl_is_newvarnode(v) || jl_is_svec(v) || jl_is_tuple(v) || jl_is_uniontype(v) || jl_is_int32(v) || jl_is_int64(v) || diff --git a/src/llvm-version.h b/src/llvm-version.h index e9c42ff400331..0149b4016cb22 100644 --- a/src/llvm-version.h +++ b/src/llvm-version.h @@ -2,6 +2,10 @@ #include +#ifndef LLVM_VERSION_PATCH // for LLVM 3.3 +#define LLVM_VERSION_PATCH 0 +#endif + // The LLVM version used, JL_LLVM_VERSION, is represented as a 5-digit integer // of the form ABBCC, where A is the major version, B is minor, and C is patch. // So for example, LLVM 3.7.0 is 30700. From 03cff8b750fd4873343e0320f66042c960629d26 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Mon, 19 Sep 2016 19:42:35 -0400 Subject: [PATCH 2/2] simplify entry points to type inference this breaks up the typeinf_edge function into its subcomponents, allowing callers to exercise only the pieces they care about rather than returning all possible values the caller might care about in a Tuple also adds missing test-and-lock-and-test for threaded inference entry points --- base/REPLCompletions.jl | 16 +- base/inference.jl | 318 ++++++++++++++++++++++------------------ base/reflection.jl | 16 +- src/jltypes.c | 2 +- 4 files changed, 192 insertions(+), 160 deletions(-) diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index 8a1ea5e6b1b0a..f07b8d454cce4 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -235,7 +235,7 @@ function find_start_brace(s::AbstractString; c_start='(', c_end=')') braces != 1 && return 0:-1, -1 method_name_end = reverseind(r, i) startind = nextind(s, rsearch(s, non_identifier_chars, method_name_end)) - return startind:endof(s), method_name_end + return (startind:endof(s), method_name_end) end # Returns the value in a expression if sym is defined in current namespace fn. @@ -249,18 +249,18 @@ function get_value(sym::Expr, fn) fn, found = get_value(ex, fn) !found && return (nothing, false) end - fn, true + return (fn, true) end get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (getfield(fn, sym), true) : (nothing, false) get_value(sym::QuoteNode, fn) = isdefined(fn, sym.value) ? (getfield(fn, sym.value), true) : (nothing, false) -get_value(sym, fn) = sym, true +get_value(sym, fn) = (sym, true) # Return the value of a getfield call expression function get_value_getfield(ex::Expr, fn) # Example :((top(getfield))(Base,:max)) val, found = get_value_getfield(ex.args[2],fn) #Look up Base in Main and returns the module found || return (nothing, false) - get_value_getfield(ex.args[3],val) #Look up max in Base and returns the function if found. + return get_value_getfield(ex.args[3], val) #Look up max in Base and returns the function if found. end get_value_getfield(sym, fn) = get_value(sym, fn) @@ -285,9 +285,9 @@ function get_type_call(expr::Expr) length(mt) == 1 || return (Any, false) m = first(mt) # Typeinference - linfo = Base.func_for_method_checked(m[3], Tuple{args...}) - (tree, return_type) = Core.Inference.typeinf(linfo, m[1], m[2]) - return return_type, true + return_type = Core.Inference.typeinf_type(m[3], m[1], m[2]) + return_type === nothing && return (Any, false) + return (return_type, true) end # Returns the return type. example: get_type(:(Base.strip("",' ')),Main) returns (String,true) function get_type(sym::Expr, fn) @@ -305,7 +305,7 @@ function get_type(sym::Expr, fn) end return get_type_call(sym) end - (Any, false) + return (Any, false) end function get_type(sym, fn) val, found = get_value(sym, fn) diff --git a/base/inference.jl b/base/inference.jl index 273c126f77ceb..a8d96544d2048 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -85,6 +85,7 @@ type InferenceState toplevel = !isdefined(linfo, :def) if !toplevel && isempty(linfo.sparam_vals) && !isempty(linfo.def.sparam_syms) + # linfo is unspecialized sp = svec(Any[ TypeVar(sym, Any, true) for sym in linfo.def.sparam_syms ]...) else sp = linfo.sparam_vals @@ -663,7 +664,7 @@ function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::InferenceState) meth = entry.func (ti, env) = ccall(:jl_match_method, Any, (Any, Any, Any), argtype, meth.sig, meth.tvars)::SimpleVector - return typeinf_edge(meth::Method, ti, env, sv)[2] + return typeinf_edge(meth::Method, ti, env, sv) end function tuple_tfunc(argtype::ANY) @@ -913,7 +914,7 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, sv) sig = recomputed[1]::DataType sparams = recomputed[2]::SimpleVector end - (_tree, rt) = typeinf_edge(method, sig, sparams, sv) + rt = typeinf_edge(method, sig, sparams, sv) rettype = tmerge(rettype, rt) if is(rettype,Any) break @@ -1469,118 +1470,32 @@ function newvar!(sv::InferenceState, typ::ANY) return SSAValue(id) end -# create a specialized MethodInstance from a method -function get_linfo(method::Method, types::ANY, sp::SimpleVector) - return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, types, sp) -end - inlining_enabled() = (JLOptions().can_inline == 1) coverage_enabled() = (JLOptions().code_coverage != 0) -#### entry points for inferring a MethodInstance given a type signature #### -function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, needtree::Bool, optimize::Bool, cached::Bool, caller) - local code = nothing - local frame = nothing - if isa(caller, MethodInstance) - code = caller - elseif cached && !is(method.specializations, nothing) - # check cached specializations - # for an existing result stored there - code = ccall(:jl_specializations_lookup, Any, (Any, Any), method, atypes) - if isa(code, Void) - # something completely new - elseif isa(code, MethodInstance) - # something existing - else - # sometimes just a return type is stored here. if a full AST - # is not needed, we can return it. - typeassert(code, Type) - if !needtree - return (nothing, code, true) - end - cached = false # don't need to save the new result - code = nothing - end +function code_for_method(method::Method, atypes::ANY, sparams::SimpleVector, preexisting::Bool=false) + if method.isstaged && !isleaftype(atypes) + # don't call staged functions on abstract types. + # (see issues #8504, #10230) + # we can't guarantee that their type behavior is monotonic. + # XXX: this test is wrong if Types (such as DataType) are present + return nothing end - - if isa(code, MethodInstance) && isdefined(code, :inferred) - if code.jlcall_api == 2 - if needtree - tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) - tree.code = Any[ Expr(:return, QuoteNode(code.inferred)) ] - tree.slotnames = Any[ compiler_temp_sym for i = 1:method.nargs ] - tree.slotflags = UInt8[ 0 for i = 1:method.nargs ] - tree.slottypes = nothing - tree.ssavaluetypes = 0 - tree.inferred = true - tree.pure = true - tree.inlineable = true - else - tree = Const(code.inferred) - end - return (tree, code.rettype, true) - elseif isa(code.inferred, CodeInfo) - if code.inferred.inferred - return (code.inferred, code.rettype, true) - end - elseif !needtree - return (nothing, code.rettype, true) - else - cached = false # don't need to save the new result - end - end - - ccall(:jl_typeinf_begin, Void, ()) - thread_in_typeinf_loop = in_typeinf_loop::Bool - ccall(:jl_typeinf_end, Void, ()) - - if caller === nothing && thread_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 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) - skip = true - if method.module == _topmod(method.module) || (isdefined(Main, :Base) && method.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 to detect this case - if method.name == :promote_type || method.name == :typejoin - skip = false - elseif method.name == :getindex || method.name == :next || method.name == :indexed_next - argtypes = atypes.parameters - if length(argtypes)>2 && argtypes[3] ⊑ Int - at2 = widenconst(argtypes[2]) - if (at2 <: Tuple || - (isa(at2, DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) && - (at2::DataType).name === Main.Base.Pair.name)) - skip = false - end - end - end - end - if skip - return (nothing, Union{}, false) + if preexisting + if !is(method.specializations, nothing) + # check cached specializations + # for an existing result stored there + return ccall(:jl_specializations_lookup, Any, (Any, Any), method, atypes) end + return nothing end + return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, atypes, sparams) +end - if isa(code, MethodInstance) - # reuse the existing code object - linfo = code - @assert typeseq(linfo.specTypes, atypes) - else - if method.isstaged && !isleaftype(atypes) - # don't call staged functions on abstract types. - # (see issues #8504, #10230) - # we can't guarantee that their type behavior is monotonic. - # XXX: this test is wrong if Types (such as DataType) are present - return (nothing, Any, false) - end - linfo = get_linfo(method, atypes, sparams) - end - ccall(:jl_typeinf_begin, Void, ()) - # XXX: the following logic is likely subtly broken if code.code was nothing, - # although it seems unlikely something bad (infinite recursion) will happen as a result +# build (and start inferring) the inference frame for the linfo +function typeinf_frame(linfo::MethodInstance, optimize::Bool, cached::Bool, caller) + frame = nothing if linfo.inInference # inference on this signature may be in progress, # find the corresponding frame in the active list @@ -1597,25 +1512,24 @@ function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, needtr else # TODO: verify again here that linfo wasn't just inferred # inference not started yet, make a new frame for a new lambda - if method.isstaged + if linfo.def.isstaged try # user code might throw errors – ignore them src = get_staged(linfo) catch - return (nothing, Any, false) + return nothing end else src = get_source(linfo) end linfo.inInference = true - frame = InferenceState(linfo::MethodInstance, src, optimize, inlining_enabled(), cached) + frame = InferenceState(linfo, src, optimize, inlining_enabled(), cached) end frame = frame::InferenceState - if isa(caller, InferenceState) + if isa(caller, InferenceState) && !caller.inferred # if we were called from inside inference, the caller will be the InferenceState object # for which the edge was required - caller = caller::InferenceState if haskey(caller.edges, frame) Ws = caller.edges[frame]::Vector{Int} if !(caller.currpc in Ws) @@ -1629,36 +1543,112 @@ function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, needtr end end typeinf_loop(frame) - ccall(:jl_typeinf_end, Void, ()) - return (frame.src, widenconst(frame.bestguess), frame.inferred) + return frame end -function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, caller) - return typeinf_edge(method, atypes, sparams, false, true, true, caller) -end -function typeinf(method::Method, atypes::ANY, sparams::SimpleVector, needtree::Bool=false) - return typeinf_edge(method, atypes, sparams, needtree, true, true, nothing) +# compute (and cache) an inferred AST and return the current best estimate of the result type +function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, caller::InferenceState) + code = code_for_method(method, atypes, sparams) + code === nothing && return Any + code = code::MethodInstance + if isdefined(code, :inferred) + # return rettype if the code is already inferred + # staged functions make this hard since they have two "inferred" conditions, + # so need to check whether the code itself is also inferred + inf = code.inferred + if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred + return code.rettype + end + end + frame = typeinf_frame(code, true, true, caller) + frame === nothing && return Any + frame = frame::InferenceState + return widenconst(frame.bestguess) end -# compute an inferred (optionally optimized) AST without global effects (i.e. updating the cache) -function typeinf_uncached(method::Method, atypes::ANY, sparams::ANY; optimize::Bool=true) - return typeinf_edge(method, atypes, sparams, true, optimize, false, nothing) + +#### entry points for inferring a MethodInstance given a type signature #### + +# compute an inferred AST and return type +function typeinf_code(method::Method, atypes::ANY, sparams::SimpleVector, optimize::Bool, cached::Bool) + code = code_for_method(method, atypes, sparams) + code === nothing && return (nothing, Any) + return typeinf_code(code::MethodInstance, optimize, cached) +end +function typeinf_code(linfo::MethodInstance, optimize::Bool, cached::Bool) + for i = 1:2 # test-and-lock-and-test + if cached && isdefined(linfo, :inferred) + # see if this code already exists in the cache + # staged functions make this hard since they have two "inferred" conditions, + # so need to check whether the code itself is also inferred + inf = linfo.inferred + if linfo.jlcall_api == 2 + method = linfo.def + tree = ccall(:jl_new_code_info_uninit, Ref{CodeInfo}, ()) + tree.code = Any[ Expr(:return, QuoteNode(inf)) ] + tree.slotnames = Any[ compiler_temp_sym for i = 1:method.nargs ] + tree.slotflags = UInt8[ 0 for i = 1:method.nargs ] + tree.slottypes = nothing + tree.ssavaluetypes = 0 + tree.inferred = true + tree.pure = true + tree.inlineable = true + i == 2 && ccall(:jl_typeinf_end, Void, ()) + return (tree, linfo.rettype) + elseif isa(inf, CodeInfo) + if (inf::CodeInfo).inferred + i == 2 && ccall(:jl_typeinf_end, Void, ()) + return (inf, linfo.rettype) + end + else + cached = false # don't need to save the new result + end + end + i == 1 && ccall(:jl_typeinf_begin, Void, ()) + end + frame = typeinf_frame(linfo, optimize, cached, nothing) + ccall(:jl_typeinf_end, Void, ()) + frame === nothing && return (nothing, Any) + frame = frame::InferenceState + frame.inferred || return (nothing, Any) + return (frame.src, widenconst(frame.bestguess)) end -function typeinf_uncached(method::Method, atypes::ANY, sparams::SimpleVector, optimize::Bool) - return typeinf_edge(method, atypes, sparams, true, optimize, false, nothing) + +# compute (and cache) an inferred AST and return the inferred return type +function typeinf_type(method::Method, atypes::ANY, sparams::SimpleVector, cached::Bool=true) + code = code_for_method(method, atypes, sparams) + code === nothing && return nothing + code = code::MethodInstance + for i = 1:2 # test-and-lock-and-test + if cached && isdefined(code, :inferred) + # see if this rettype already exists in the cache + # staged functions make this hard since they have two "inferred" conditions, + # so need to check whether the code itself is also inferred + inf = code.inferred + if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred + i == 2 && ccall(:jl_typeinf_end, Void, ()) + return code.rettype + end + end + i == 1 && ccall(:jl_typeinf_begin, Void, ()) + end + frame = typeinf_frame(code, cached, cached, nothing) + ccall(:jl_typeinf_end, Void, ()) + frame === nothing && return nothing + frame = frame::InferenceState + frame.inferred || return nothing + return widenconst(frame.bestguess) end + function typeinf_ext(linfo::MethodInstance) if isdefined(linfo, :def) # method lambda - infer this specialization via the method cache - if isdefined(linfo, :inferred) && isa(linfo.inferred, CodeInfo) - return linfo.inferred - end - (code, typ, inferred) = typeinf_edge(linfo.def, linfo.specTypes, linfo.sparam_vals, true, true, true, linfo) + (code, typ) = typeinf_code(linfo, true, true) return code else # toplevel lambda - infer directly linfo.inInference = true ccall(:jl_typeinf_begin, Void, ()) - frame = InferenceState(linfo, linfo.inferred, true, inlining_enabled(), true) + frame = InferenceState(linfo, linfo.inferred::CodeInfo, true, inlining_enabled(), true) typeinf_loop(frame) ccall(:jl_typeinf_end, Void, ()) @assert frame.inferred # TODO: deal with this better @@ -1666,6 +1656,7 @@ function typeinf_ext(linfo::MethodInstance) end end +#### do the work of inference #### in_typeinf_loop = false function typeinf_loop(frame) @@ -2000,9 +1991,9 @@ function finish(me::InferenceState) end if me.cached - # TODO: check that mutating the lambda info is OK first? - if !const_api - if isdefined(me.linfo, :def) + toplevel = !isdefined(me.linfo, :def) + if !toplevel + if !const_api keeptree = me.src.inlineable || ccall(:jl_is_cacheable_sig, Int32, (Any, Any, Any), me.linfo.specTypes, me.linfo.def.sig, me.linfo.def) != 0 if !keeptree @@ -2581,17 +2572,62 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference isa(si, TypeVar) && return NF end - (src, rettype, inferred) = typeinf(method, metharg, methsp, false) - if src === nothing || !inferred - return invoke_NF() + # see if the method has been previously inferred (and cached) + linfo = code_for_method(method, metharg, methsp, true) + if isa(linfo, MethodInstance) && isdefined(linfo, :inferred) + src = (linfo::MethodInstance).inferred + elseif !method.isstaged + # 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) + force_infer = false + if method.module == _topmod(method.module) || (isdefined(Main, :Base) && method.module == Main.Base) + # however, some gf have special tfunc, meaning they wouldn't have been inferred yet + # check the same conditions from abstract_call to detect this case + if method.name == :promote_type || method.name == :typejoin + force_infer = true + elseif method.name == :getindex || method.name == :next || method.name == :indexed_next + if length(atypes) > 2 && atypes[3] ⊑ Int + at2 = widenconst(atypes[2]) + if (at2 <: Tuple || + (isa(at2, DataType) && isdefined(Main, :Base) && isdefined(Main.Base, :Pair) && + (at2::DataType).name === Main.Base.Pair.name)) + force_infer = true + end + end + end + end + frame = nothing + if force_infer + if !isa(linfo, MethodInstance) + linfo = code_for_method(method, metharg, methsp) + end + if isa(linfo, MethodInstance) + frame = typeinf_frame(linfo::MethodInstance, true, true, nothing) + end + end + if isa(frame, InferenceState) && frame.inferred + linfo = frame.linfo + src = frame.src + else + linfo = nothing + src = nothing + end + else + linfo = nothing + src = nothing end - if isa(src, Const) + + if isa(linfo, MethodInstance) && linfo.jlcall_api == 2 # in this case function can be inlined to a constant - return inline_as_constant(src.val, argexprs, sv) - elseif !isa(src, CodeInfo) || !src.inlineable + return inline_as_constant(linfo.inferred, argexprs, sv) + end + + if !isa(src, CodeInfo) || !src.inferred || !src.inlineable return invoke_NF() end ast = src.code + rettype = linfo.rettype spvals = Any[] for i = 1:length(methsp) @@ -3875,8 +3911,8 @@ end function return_type(f::ANY, t::ANY) rt = Union{} for m in _methods(f, t, -1) - _, ty, inferred = typeinf(m[3], m[1], m[2], false) - !inferred && return Any + ty = typeinf_type(m[3], m[1], m[2]) + ty === nothing && return Any rt = tmerge(rt, ty) rt === Any && break end @@ -3889,8 +3925,8 @@ end # 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], m[1], m[2], true) + typeinf_type(m[3], m[1], m[2]) end for m in _methods_by_ftype(Tuple{typeof(typeinf_edge), Vararg{Any}}, 10) - typeinf(m[3], m[1], m[2], true) + typeinf_type(m[3], m[1], m[2]) end diff --git a/base/reflection.jl b/base/reflection.jl index d9cb9d33fc3ea..c4577ea904862 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -575,14 +575,10 @@ function code_typed(f::ANY, types::ANY=Tuple; optimize=true) end types = to_tuple_type(types) asts = [] - for x in _methods(f,types,-1) + for x in _methods(f, types, -1) meth = func_for_method_checked(x[3], types) - if optimize - (code, ty, inf) = Core.Inference.typeinf(meth, x[1], x[2], true) - else - (code, ty, inf) = Core.Inference.typeinf_uncached(meth, x[1], x[2], optimize=false) - end - inf || error("inference not successful") # Inference disabled + (code, ty) = Core.Inference.typeinf_code(meth, x[1], x[2], optimize, !optimize) + code === nothing && error("inference not successful") # Inference disabled? push!(asts, uncompressed_ast(meth, code) => ty) end return asts @@ -595,10 +591,10 @@ function return_types(f::ANY, types::ANY=Tuple) end types = to_tuple_type(types) rt = [] - for x in _methods(f,types,-1) + for x in _methods(f, types, -1) meth = func_for_method_checked(x[3], types) - (code, ty, inf) = Core.Inference.typeinf(meth, x[1], x[2]) - inf || error("inference not successful") # Inference disabled + ty = Core.Inference.typeinf_type(meth, x[1], x[2]) + ty === nothing && error("inference not successful") # Inference disabled? push!(rt, ty) end return rt diff --git a/src/jltypes.c b/src/jltypes.c index a5f6fa6bd82a3..45aa6c7a70f7d 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3946,7 +3946,7 @@ void jl_init_types(void) jl_any_type, // void* jl_any_type, // void* jl_any_type, jl_any_type), // void*, void* - 0, 1, 4); + 0, 1, 3); jl_typector_type = jl_new_datatype(jl_symbol("TypeConstructor"),