From 0b8f0b7dc406aabdd1de700b716c5fe700fa5cca Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 7 Jun 2016 16:14:14 -0400 Subject: [PATCH 1/8] this fixes #265, mostly adds a world counter and threads it everywhere and tracks all backedges add tests for some versions of #265 also updates the compile test to be world-aware and add docs for method replacement / world age note that MethodTables need to Base.serialized in a world-aware manner and MethodError needs to be world aware the age in MethodInstance is a copy of the value returned by typemap lookup since we don't currently have a mechanism for accessing that value directly plus, this seems easier anyways handle MethodInstance age range updates correctly means only updating them in-place when that doesn't potentially invalidate other consumers that might also be holding a reference to it and propagate this through the callers to type-inference virtual edges are tracked via exact signature tuple types inference is pure for backedges meaning that we only add backedges that exist at the end of inference and then add them to the correct MethodInstance object which also make codegen caching world-aware min/max validity of ml-matches results is threaded out of jl_matching_methods into inference eventually should probably switch this to use a Ref, or return the value, or something similarly more efficient than a singleton Array, but currently refpointer.jl is not part of Core.Inference incremental deserialize is aware of world counter logic by handling the follow cases: - "free-standing" methods (where age is at the default of 0), probably from a toplevel thunk or deserialize - garbage methods (really we just want to delete these, but, oh well). this is something inferred to have been replaced - applicable methods (this is the overwhelming majority of cases) the logic between TypeMapEntry, Method, and MethodInstance all agree on these concepts this uses Expr(:body) as an argument to eval to ensure that eval won't call expand, which ensures that QuoteNode works correctly and the eval is reasonably fast --- base/REPL.jl | 6 +- base/REPLCompletions.jl | 5 +- base/base.jl | 3 + base/boot.jl | 10 + base/client.jl | 7 +- base/coreimg.jl | 5 - base/docs/core.jl | 2 +- base/error.jl | 6 +- base/inference.jl | 550 +++++++++++++++------- base/methodshow.jl | 10 +- base/multi.jl | 8 +- base/reflection.jl | 71 +-- base/replutil.jl | 16 +- base/serialize.jl | 14 +- base/sparse/sparsematrix.jl | 4 +- base/sysimg.jl | 6 - doc/src/devdocs/ast.md | 21 +- doc/src/manual/metaprogramming.md | 47 ++ doc/src/manual/methods.md | 108 +++++ src/alloc.c | 22 +- src/ast.c | 9 +- src/builtin_proto.h | 3 +- src/builtins.c | 34 +- src/ccall.cpp | 3 + src/codegen.cpp | 210 +++++---- src/dump.c | 239 ++++++++-- src/gc.c | 3 + src/gf.c | 742 ++++++++++++++++++++++-------- src/init.c | 3 + src/interpreter.c | 37 +- src/jl_uv.c | 6 +- src/jlapi.c | 18 + src/jltypes.c | 95 ++-- src/julia.h | 19 +- src/julia_internal.h | 46 +- src/julia_threads.h | 1 + src/task.c | 8 +- src/toplevel.c | 39 +- src/typemap.c | 118 +++-- test/choosetests.jl | 3 +- test/compile.jl | 16 +- test/core.jl | 2 +- test/loading.jl | 3 +- test/reflection.jl | 18 +- test/runtests.jl | 2 +- test/staged.jl | 7 +- test/worlds.jl | 152 ++++++ ui/repl.c | 3 + 48 files changed, 2017 insertions(+), 743 deletions(-) create mode 100644 test/worlds.jl diff --git a/base/REPL.jl b/base/REPL.jl index 4c1bfd799db4d..7bdebcc6cf676 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -66,7 +66,7 @@ function eval_user_input(ast::ANY, backend::REPLBackend) value = eval(Main, ast) backend.in_eval = false # note: value wrapped in a closure to ensure it doesn't get passed through expand - eval(Main, Expr(:(=), :ans, Expr(:call, ()->value))) + eval(Main, Expr(:body, Expr(:(=), :ans, QuoteNode(value)), Expr(:return, nothing))) put!(backend.response_channel, (value, nothing)) end break @@ -153,9 +153,9 @@ function print_response(errio::IO, val::ANY, bt, show_value::Bool, have_color::B if val !== nothing && show_value try if specialdisplay === nothing - display(val) + eval(Main, Expr(:body, Expr(:return, Expr(:call, display, QuoteNode(val))))) else - display(specialdisplay,val) + eval(Main, Expr(:body, Expr(:return, Expr(:call, specialdisplay, QuoteNode(val))))) end catch err println(errio, "Error showing value of type ", typeof(val), ":") diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index 99f6b50428e1a..03e84e0381168 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -281,11 +281,12 @@ function get_type_call(expr::Expr) found ? push!(args, typ) : push!(args, Any) end # use _methods_by_ftype as the function is supplied as a type - mt = Base._methods_by_ftype(Tuple{ft, args...}, -1) + world = typemax(UInt) + mt = Base._methods_by_ftype(Tuple{ft, args...}, -1, world) length(mt) == 1 || return (Any, false) m = first(mt) # Typeinference - params = Core.Inference.InferenceParams() + params = Core.Inference.InferenceParams(world) return_type = Core.Inference.typeinf_type(m[3], m[1], m[2], true, params) return_type === nothing && return (Any, false) return (return_type, true) diff --git a/base/base.jl b/base/base.jl index 9d9c41cac0fad..b349101bff15b 100644 --- a/base/base.jl +++ b/base/base.jl @@ -57,7 +57,10 @@ Alternatively, there is no unique most-specific method. type MethodError <: Exception f args + world::UInt + MethodError(f::ANY, args::ANY, world::UInt) = new(f, args, world) end +MethodError(f::ANY, args::ANY) = MethodError(f, args, typemax(UInt)) """ EOFError() diff --git a/base/boot.jl b/base/boot.jl index f487b01279f37..c9e1b1a800c00 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -308,6 +308,7 @@ unsafe_convert{T}(::Type{T}, x::T) = x typealias NTuple{N,T} Tuple{Vararg{T,N}} + # primitive array constructors (::Type{Array{T,N}}){T,N}(d::NTuple{N,Int}) = ccall(:jl_new_array, Array{T,N}, (Any,Any), Array{T,N}, d) @@ -338,6 +339,15 @@ Array{T}(::Type{T}, m::Int,n::Int) = Array{T,2}(m,n) Array{T}(::Type{T}, m::Int,n::Int,o::Int) = Array{T,3}(m,n,o) +# primitive Symbol constructors +Symbol(s::String) = Symbol(s.data) +function Symbol(a::Array{UInt8,1}) + return ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int), + ccall(:jl_array_ptr, Ptr{UInt8}, (Any,), a), + Intrinsics.arraylen(a)) +end + + # docsystem basics macro doc(x...) atdoc(x...) diff --git a/base/client.jl b/base/client.jl index eff0589986a0c..5b1e35fcb1e5b 100644 --- a/base/client.jl +++ b/base/client.jl @@ -142,12 +142,13 @@ function eval_user_input(ast::ANY, show_value) else ast = expand(ast) value = eval(Main, ast) - eval(Main, Expr(:(=), :ans, Expr(:call, ()->value))) - if value!==nothing && show_value + eval(Main, Expr(:body, Expr(:(=), :ans, QuoteNode(value)), Expr(:return, nothing))) + if !(value === nothing) && show_value if have_color print(answer_color()) end - try display(value) + try + eval(Main, Expr(:body, Expr(:return, Expr(:call, display, QuoteNode(value))))) catch err println(STDERR, "Evaluation succeeded, but an error occurred while showing value of type ", typeof(value), ":") rethrow(err) diff --git a/base/coreimg.jl b/base/coreimg.jl index 9f7b4da180552..6e831935e4b40 100644 --- a/base/coreimg.jl +++ b/base/coreimg.jl @@ -38,11 +38,6 @@ if !isdefined(Main, :Base) (::Type{T}){T}(arg) = convert(T, arg)::T end -# Symbol constructors -Symbol(s::String) = Symbol(s.data) -Symbol(a::Array{UInt8,1}) = - ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int32), a, length(a)) - # core array operations include("array.jl") include("abstractarray.jl") diff --git a/base/docs/core.jl b/base/docs/core.jl index 4934f4e43aaa4..81993ae2ef2c3 100644 --- a/base/docs/core.jl +++ b/base/docs/core.jl @@ -7,7 +7,7 @@ import ..esc, ..push!, ..getindex, ..current_module, ..unsafe_load, ..Csize_t function doc!(str, ex) ptr = unsafe_load(Core.Intrinsics.cglobal(:jl_filename, Ptr{UInt8})) len = ccall(:strlen, Csize_t, (Ptr{UInt8},), ptr) - file = ccall(:jl_symbol_n, Any, (Ptr{UInt8}, Int32), ptr, len) + file = ccall(:jl_symbol_n, Any, (Ptr{UInt8}, Csize_t), ptr, len) line = unsafe_load(Core.Intrinsics.cglobal(:jl_lineno, Int32)) # Cint push!(DOCS, (current_module(), ex, str, file, line)) end diff --git a/base/error.jl b/base/error.jl index 9beef3b93bdd7..6e9e6cac867c9 100644 --- a/base/error.jl +++ b/base/error.jl @@ -38,7 +38,9 @@ systemerror(p, b::Bool; extrainfo=nothing) = b ? throw(Main.Base.SystemError(str assert(x) = x ? nothing : throw(Main.Base.AssertionError()) macro assert(ex, msgs...) msg = isempty(msgs) ? ex : msgs[1] - if !isempty(msgs) && (isa(msg, Expr) || isa(msg, Symbol)) + if isa(msg, AbstractString) + msg = msg # pass-through + elseif !isempty(msgs) && (isa(msg, Expr) || isa(msg, Symbol)) # message is an expression needing evaluating msg = :(Main.Base.string($(esc(msg)))) elseif isdefined(Main, :Base) && isdefined(Main.Base, :string) @@ -47,7 +49,7 @@ macro assert(ex, msgs...) # string() might not be defined during bootstrap msg = :(Main.Base.string($(Expr(:quote,msg)))) end - :($(esc(ex)) ? $(nothing) : throw(Main.Base.AssertionError($msg))) + return :($(esc(ex)) ? $(nothing) : throw(Main.Base.AssertionError($msg))) end # NOTE: Please keep the constant values specified below in sync with the doc string diff --git a/base/inference.jl b/base/inference.jl index bd0471afd5dd9..6f6ad506edadb 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -7,6 +7,8 @@ const MAX_TYPEUNION_LEN = 3 const MAX_TYPE_DEPTH = 7 immutable InferenceParams + world::UInt + # optimization inlining::Bool @@ -17,11 +19,15 @@ immutable InferenceParams MAX_UNION_SPLITTING::Int # reasonable defaults - InferenceParams(;inlining::Bool=inlining_enabled(), - tupletype_len::Int=15, tuple_depth::Int=4, - tuple_splat::Int=16, union_splitting::Int=4) = - new(inlining, tupletype_len, + function InferenceParams(world::UInt; + inlining::Bool = inlining_enabled(), + tupletype_len::Int = 15, + tuple_depth::Int = 4, + tuple_splat::Int = 16, + union_splitting::Int = 4) + return new(world, inlining, tupletype_len, tuple_depth, tuple_splat, union_splitting) + end end const UNION_SPLIT_MISMATCH_ERROR = false @@ -63,13 +69,15 @@ type InferenceState mod::Module currpc::LineNum - params::InferenceParams - # info on the state of inference and the linfo + params::InferenceParams linfo::MethodInstance # used here for the tuple (specTypes, env, Method) src::CodeInfo + min_valid::UInt + max_valid::UInt nargs::Int stmt_types::Vector{Any} + stmt_edges::Vector{Any} # return type bestguess #::Type # current active instruction pointers @@ -86,11 +94,13 @@ type InferenceState # call-graph edges connecting from a caller to a callee (and back) # we shouldn't need to iterate edges very often, so we use it to optimize the lookup from edge -> linenum # whereas backedges is optimized for iteration - edges::ObjectIdDict #Dict{InferenceState, Vector{LineNum}} + edges::ObjectIdDict # a Dict{InferenceState, Vector{LineNum}} backedges::Vector{Tuple{InferenceState, Vector{LineNum}}} # iteration fixed-point detection fixedpoint::Bool inworkq::Bool + const_api::Bool + const_ret::Bool # TODO: put these in InferenceParams (depends on proper multi-methodcache support) optimize::Bool @@ -117,9 +127,10 @@ type InferenceState src.ssavaluetypes = Any[ NF for i = 1:(src.ssavaluetypes::Int) ] n = length(code) - s = Any[ () for i = 1:n ] + s_types = Any[ () for i = 1:n ] # initial types - s[1] = Any[ VarState(Bottom, true) for i = 1:nslots ] + s_types[1] = Any[ VarState(Bottom, true) for i = 1:nslots ] + s_edges = Any[ () for i = 1:n ] atypes = linfo.specTypes nargs = toplevel ? 0 : linfo.def.nargs @@ -130,9 +141,9 @@ type InferenceState if la > 1 atypes = Tuple{Any[Any for i = 1:(la - 1)]..., Tuple.parameters[1]} end - s[1][la] = VarState(Tuple, false) + s_types[1][la] = VarState(Tuple, false) else - s[1][la] = VarState(tuple_tfunc(limit_tuple_depth(params, tupletype_tail(atypes, la))), false) + s_types[1][la] = VarState(tuple_tfunc(limit_tuple_depth(params, tupletype_tail(atypes, la))), false) end la -= 1 end @@ -164,10 +175,10 @@ type InferenceState # replace singleton types with their equivalent Const object atyp = Const(atyp.instance) end - s[1][i] = VarState(atyp, false) + s_types[1][i] = VarState(atyp, false) end for i = (laty + 1):la - s[1][i] = VarState(lastatype, false) + s_types[1][i] = VarState(lastatype, false) end else @assert la == 0 # wrong number of arguments @@ -184,16 +195,29 @@ type InferenceState W = IntSet() push!(W, 1) #initial pc to visit - inmodule = toplevel ? current_module() : linfo.def.module # toplevel thunks are inferred in the current module + if !toplevel + meth = linfo.def + inmodule = meth.module + else + inmodule = current_module() # toplevel thunks are inferred in the current module + end + + if cached && !toplevel + min_valid = min_world(linfo.def) + max_valid = max_world(linfo.def) + else + min_valid = typemax(UInt) + max_valid = typemin(UInt) + end frame = new( - sp, nl, inmodule, 0, - params, - linfo, src, nargs, s, Union{}, W, 1, n, + sp, nl, inmodule, 0, params, + linfo, src, min_valid, max_valid, + nargs, s_types, s_edges, Union{}, W, 1, n, cur_hand, handler_at, n_handlers, ssavalue_uses, ssavalue_init, - ObjectIdDict(), #Dict{InferenceState, Vector{LineNum}}(), + ObjectIdDict(), # Dict{InferenceState, Vector{LineNum}}(), Vector{Tuple{InferenceState, Vector{LineNum}}}(), - false, false, optimize, cached, false) + false, false, false, false, optimize, cached, false) push!(active, frame) nactive[] += 1 return frame @@ -703,7 +727,7 @@ function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::InferenceState) ft = type_typeof(f) types = Tuple{ft, types.parameters...} argtype = Tuple{ft, argtype.parameters...} - entry = ccall(:jl_gf_invoke_lookup, Any, (Any,), types) + entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), types, sv.params.world) if entry === nothing return Any end @@ -829,7 +853,7 @@ end #### recursing into expression #### -function abstract_call_gf_by_type(f::ANY, argtype::ANY, sv::InferenceState) +function abstract_call_gf_by_type(f::ANY, atype::ANY, sv::InferenceState) tm = _topmod(sv) # don't consider more than N methods. this trades off between # compiler performance and generated code performance. @@ -838,27 +862,43 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, sv::InferenceState) # 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. # here I picked 4. - argtype = limit_tuple_type(argtype, sv.params) + argtype = limit_tuple_type(atype, sv.params) argtypes = argtype.parameters - applicable = _methods_by_ftype(argtype, 4) + ft = argtypes[1] # TODO: ccall jl_first_argument_datatype here + isa(ft, DataType) || return Any # the function being called is unknown. can't properly handle this backedge right now + isdefined(ft.name, :mt) || return Any # not callable. should be Bottom, but can't track this backedge right now + if ft.name === Type.name + tname = ft.parameters[1] + if isa(tname, TypeVar) + tname = tname.ub + end + if isa(tname, TypeConstructor) + tname = tname.body + end + if !isa(tname, DataType) + # can't track the backedge to the ctor right now + # for things like Union + return Any + end + end + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] + applicable = _methods_by_ftype(argtype, 4, sv.params.world, min_valid, max_valid) rettype = Bottom if applicable === false # this means too many methods matched return Any end x::Array{Any,1} = applicable - if isempty(x) - # no methods match - # TODO: it would be nice to return Bottom here, but during bootstrap we - # often compile code that calls methods not defined yet, so it is much - # safer just to fall back on dynamic dispatch. - return Any - end + fullmatch = false for (m::SimpleVector) in x sig = m[1]::DataType method = m[3]::Method sparams = m[2]::SimpleVector recomputesvec = false + if !fullmatch && typeseq(sig, argtype) + fullmatch = true + end # limit argument type tuple growth lsig = length(m[3].sig.parameters) @@ -969,7 +1009,16 @@ function abstract_call_gf_by_type(f::ANY, argtype::ANY, sv::InferenceState) break end end - # if rettype is Bottom we've found a method not found error + if !(fullmatch || rettype === Any) + # also need an edge to the method table in case something gets + # added that did not intersect with any existing method + add_mt_backedge(ft.name.mt, argtype, sv) + update_valid_age!(min_valid[1], max_valid[1], sv) + end + if isempty(x) + # TODO: this is needed because type intersection is wrong in some cases + return Any + end #print("=> ", rettype, "\n") return rettype end @@ -1073,7 +1122,9 @@ function pure_eval_call(f::ANY, argtypes::ANY, atype::ANY, vtypes::VarTable, sv: end end - meth = _methods_by_ftype(atype, 1) + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] + meth = _methods_by_ftype(atype, 1, sv.params.world, min_valid, max_valid) if meth === false || length(meth) != 1 return false end @@ -1086,7 +1137,9 @@ function pure_eval_call(f::ANY, argtypes::ANY, atype::ANY, vtypes::VarTable, sv: args = Any[ (a=argtypes[i]; isa(a,Const) ? a.val : a.parameters[1]) for i in 2:length(argtypes) ] try - return abstract_eval_constant(f(args...)) + value = Core._apply_pure(f, args) + # TODO: add some sort of edge(s) + return abstract_eval_constant(value) catch return false end @@ -1552,7 +1605,90 @@ end inlining_enabled() = (JLOptions().can_inline == 1) coverage_enabled() = (JLOptions().code_coverage != 0) -function code_for_method(method::Method, atypes::ANY, sparams::SimpleVector, preexisting::Bool=false) +# TODO: track the worlds for which this InferenceState +# is being used, and split it if the WIP requires it? +function converge_valid_age!(sv::InferenceState) + # push the validity range of sv into its fixedpoint callers + # recursing as needed to cover the graph + for (i, _) in sv.backedges + if i.fixedpoint + updated = false + if i.min_valid < sv.min_valid + i.min_valid = sv.min_valid + updated = true + end + if i.max_valid > sv.max_valid + i.max_valid = sv.max_valid + updated = true + end + @assert !isdefined(i.linfo, :def) || !i.cached || i.min_valid <= i.params.world <= i.max_valid "invalid age range update" + if updated + converge_valid_age!(i) + end + end + end + nothing +end + +# work towards converging the valid age range for sv +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 !isdefined(sv.linfo, :def) || !sv.cached || sv.min_valid <= sv.params.world <= sv.max_valid "invalid age range update" + nothing +end +update_valid_age!(edge::InferenceState, sv::InferenceState) = update_valid_age!(edge.min_valid, edge.max_valid, sv) +update_valid_age!(li::MethodInstance, sv::InferenceState) = update_valid_age!(min_world(li), max_world(li), sv) + +# temporarily accumulate our edges to later add as backedges in the callee +function add_backedge(li::MethodInstance, caller::InferenceState) + isdefined(caller.linfo, :def) || return # don't add backedges to toplevel exprs + if caller.stmt_edges[caller.currpc] === () + caller.stmt_edges[caller.currpc] = [] + end + push!(caller.stmt_edges[caller.currpc], li) + update_valid_age!(li, caller) + nothing +end + +# temporarily accumulate our no method errors to later add as backedges in the callee method table +function add_mt_backedge(mt::MethodTable, typ::ANY, caller::InferenceState) + isdefined(caller.linfo, :def) || return # don't add backedges to toplevel exprs + if caller.stmt_edges[caller.currpc] === () + caller.stmt_edges[caller.currpc] = [] + end + push!(caller.stmt_edges[caller.currpc], mt) + push!(caller.stmt_edges[caller.currpc], typ) + nothing +end + +# add the real backedges now +function finalize_backedges(frame::InferenceState) + toplevel = !isdefined(frame.linfo, :def) + if !toplevel && frame.cached && frame.max_valid == typemax(UInt) + caller = frame.linfo + for edges in frame.stmt_edges + i = 1 + while i <= length(edges) + to = edges[i] + if isa(to, MethodInstance) + ccall(:jl_method_instance_add_backedge, Void, (Any, Any), to, caller) + i += 1 + else + typeassert(to, MethodTable) + typ = edges[i + 1] + ccall(:jl_method_table_add_backedge, Void, (Any, Any, Any), to, typ, caller) + i += 2 + end + end + end + end +end + +function code_for_method(method::Method, atypes::ANY, sparams::SimpleVector, world::UInt, preexisting::Bool=false) + if world < min_world(method) || world > max_world(method) + return nothing + end if method.isstaged && !isleaftype(atypes) # don't call staged functions on abstract types. # (see issues #8504, #10230) @@ -1564,33 +1700,48 @@ function code_for_method(method::Method, atypes::ANY, sparams::SimpleVector, pre if method.specializations !== nothing # check cached specializations # for an existing result stored there - return ccall(:jl_specializations_lookup, Any, (Any, Any), method, atypes) + return ccall(:jl_specializations_lookup, Any, (Any, Any, UInt), method, atypes, world) end return nothing end - return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), method, atypes, sparams) + return ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any, UInt), method, atypes, sparams, world) +end + +function typeinf_active(linfo::MethodInstance) + for infstate in active + infstate === nothing && continue + infstate = infstate::InferenceState + if linfo === infstate.linfo + return infstate + end + end + return nothing end +function add_backedge(frame::InferenceState, caller::InferenceState, currpc::Int) + update_valid_age!(frame, caller) + if haskey(caller.edges, frame) + Ws = caller.edges[frame]::Vector{Int} + if !(currpc in Ws) + push!(Ws, currpc) + end + else + Ws = Int[currpc] + caller.edges[frame] = Ws + push!(frame.backedges, (caller, Ws)) + end +end # build (and start inferring) the inference frame for the linfo function typeinf_frame(linfo::MethodInstance, caller, optimize::Bool, cached::Bool, params::InferenceParams) - frame = nothing - if linfo.inInference + if cached && linfo.inInference # inference on this signature may be in progress, # find the corresponding frame in the active list - for infstate in active - infstate === nothing && continue - infstate = infstate::InferenceState - if linfo === infstate.linfo - frame = infstate - break - end - end + frame = typeinf_active(linfo) # TODO: this assertion seems iffy assert(frame !== nothing) else - # TODO: verify again here that linfo wasn't just inferred # inference not started yet, make a new frame for a new lambda if linfo.def.isstaged try @@ -1602,25 +1753,16 @@ function typeinf_frame(linfo::MethodInstance, caller, optimize::Bool, cached::Bo else src = get_source(linfo) end - linfo.inInference = true + cached && (linfo.inInference = true) frame = InferenceState(linfo, src, optimize, cached, params) end frame = frame::InferenceState - if isa(caller, InferenceState) && !caller.inferred + if isa(caller, InferenceState) # if we were called from inside inference, the caller will be the InferenceState object # for which the edge was required - if haskey(caller.edges, frame) - Ws = caller.edges[frame]::Vector{Int} - if !(caller.currpc in Ws) - push!(Ws, caller.currpc) - end - else - @assert caller.currpc > 0 - Ws = Int[caller.currpc] - caller.edges[frame] = Ws - push!(frame.backedges, (caller, Ws)) - end + @assert caller.currpc > 0 + add_backedge(frame, caller, caller.currpc) end typeinf_loop(frame) return frame @@ -1628,7 +1770,7 @@ end # 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 = code_for_method(method, atypes, sparams, caller.params.world) code === nothing && return Any code = code::MethodInstance if isdefined(code, :inferred) @@ -1637,6 +1779,7 @@ function typeinf_edge(method::Method, atypes::ANY, sparams::SimpleVector, caller # so need to check whether the code itself is also inferred inf = code.inferred if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred + add_backedge(code, caller) if isdefined(code, :inferred_const) return abstract_eval_constant(code.inferred_const) else @@ -1655,57 +1798,59 @@ end # compute an inferred AST and return type function typeinf_code(method::Method, atypes::ANY, sparams::SimpleVector, optimize::Bool, cached::Bool, params::InferenceParams) - code = code_for_method(method, atypes, sparams) + code = code_for_method(method, atypes, sparams, params.world) code === nothing && return (nothing, Any) return typeinf_code(code::MethodInstance, optimize, cached, params) end function typeinf_code(linfo::MethodInstance, optimize::Bool, cached::Bool, params::InferenceParams) for i = 1:2 # test-and-lock-and-test + i == 2 && ccall(:jl_typeinf_begin, Void, ()) 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 + if min_world(linfo) <= params.world <= max_world(linfo) + 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 (inf, linfo.rettype) + return svec(linfo, tree, linfo.rettype) + elseif isa(inf, CodeInfo) + if (inf::CodeInfo).inferred + i == 2 && ccall(:jl_typeinf_end, Void, ()) + return svec(linfo, inf, linfo.rettype) + end 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, nothing, optimize, cached, params) ccall(:jl_typeinf_end, Void, ()) - frame === nothing && return (nothing, Any) + frame === nothing && return svec(nothing, nothing, Any) frame = frame::InferenceState - frame.inferred || return (nothing, Any) - return (frame.src, widenconst(frame.bestguess)) + frame.inferred || return svec(nothing, nothing, Any) + frame.cached || return svec(nothing, frame.src, widenconst(frame.bestguess)) + return svec(frame.linfo, frame.src, widenconst(frame.bestguess)) end # compute (and cache) an inferred AST and return the inferred return type function typeinf_type(method::Method, atypes::ANY, sparams::SimpleVector, cached::Bool, params::InferenceParams) - code = code_for_method(method, atypes, sparams) + code = code_for_method(method, atypes, sparams, params.world) code === nothing && return nothing code = code::MethodInstance for i = 1:2 # test-and-lock-and-test + i == 2 && ccall(:jl_typeinf_begin, Void, ()) 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, @@ -1716,7 +1861,6 @@ function typeinf_type(method::Method, atypes::ANY, sparams::SimpleVector, return code.rettype end end - i == 1 && ccall(:jl_typeinf_begin, Void, ()) end frame = typeinf_frame(code, nothing, cached, cached, params) ccall(:jl_typeinf_end, Void, ()) @@ -1726,21 +1870,21 @@ function typeinf_type(method::Method, atypes::ANY, sparams::SimpleVector, return widenconst(frame.bestguess) end -function typeinf_ext(linfo::MethodInstance) +function typeinf_ext(linfo::MethodInstance, world::UInt) if isdefined(linfo, :def) # method lambda - infer this specialization via the method cache - (code, typ) = typeinf_code(linfo, true, true, InferenceParams()) - return code + return typeinf_code(linfo, true, true, InferenceParams(world)) else # toplevel lambda - infer directly linfo.inInference = true ccall(:jl_typeinf_begin, Void, ()) frame = InferenceState(linfo, linfo.inferred::CodeInfo, - true, true, InferenceParams()) + true, true, InferenceParams(world)) typeinf_loop(frame) ccall(:jl_typeinf_end, Void, ()) @assert frame.inferred # TODO: deal with this better - return frame.src + @assert frame.linfo === linfo + return svec(linfo, frame.src, linfo.rettype) end end @@ -1781,7 +1925,21 @@ function typeinf_loop(frame) end end for i in length(fplist):-1:1 - finish(fplist[i]) # this may add incomplete work to active + # optimize and record the results + # the reverse order makes it more likely to inline a callee into its caller + optimize(fplist[i]::InferenceState) # this may add incomplete work to active + end + for i in fplist + # push valid ages from each node across the graph cycle + converge_valid_age!(i::InferenceState) + end + for i in fplist + # record the results + finish(i::InferenceState) + end + for i in fplist + # update and record all of the back edges for the finished world + finalize_backedges(i::InferenceState) end end end @@ -1830,6 +1988,7 @@ function typeinf_frame(frame) delete!(W, pc) frame.currpc = pc frame.cur_hand = frame.handler_at[pc] + frame.stmt_edges[pc] === () || empty!(frame.stmt_edges[pc]) stmt = frame.src.code[pc] changes = abstract_interpret(stmt, s[pc]::Array{Any,1}, frame) if changes === () @@ -1969,13 +2128,18 @@ function typeinf_frame(frame) if finished || frame.fixedpoint if finished + optimize(frame) finish(frame) + finalize_backedges(frame) else # fixedpoint propagation - for (i,_) in frame.edges + for (i, _) in frame.edges i = i::InferenceState if !i.fixedpoint - i.inworkq || push!(workq, i) - i.inworkq = true + update_valid_age!(i, frame) # work towards converging age at the same time + if !i.inworkq + push!(workq, i) + i.inworkq = true + end i.fixedpoint = true end end @@ -1992,7 +2156,7 @@ function unmark_fixedpoint(frame::InferenceState) # based upon (recursively) assuming that frame was stuck if frame.fixedpoint frame.fixedpoint = false - for (i,_) in frame.backedges + for (i, _) in frame.backedges unmark_fixedpoint(i) end end @@ -2024,10 +2188,11 @@ function isinlineable(m::Method, src::CodeInfo) end # inference completed on `me` -# update the MethodInstance and notify the edges -function finish(me::InferenceState) - for (i,_) in me.edges - @assert (i::InferenceState).fixedpoint +# now converge the optimization work +function optimize(me::InferenceState) + for (i, _) in me.edges + i = i::InferenceState + @assert i.fixedpoint end # below may call back into inference and # see this InferenceState is in an incomplete state @@ -2044,9 +2209,8 @@ function finish(me::InferenceState) end type_annotate!(me) - do_coverage = coverage_enabled() - force_noinline = false # run optimization passes on fulltree + force_noinline = false if me.optimize # This pass is required for the AST to be valid in codegen # if any `SSAValue` is created by type inference. Ref issue #6068 @@ -2060,6 +2224,7 @@ function finish(me::InferenceState) getfield_elim_pass!(me) # Clean up for `alloc_elim_pass!` and `getfield_elim_pass!` void_use_elim_pass!(me) + do_coverage = coverage_enabled() meta_elim_pass!(me.src.code::Array{Any,1}, me.src.propagate_inbounds, do_coverage) # Pop metadata before label reindexing force_noinline = popmeta!(me.src.code::Array{Any,1}, :noinline)[1] @@ -2067,94 +2232,135 @@ function finish(me::InferenceState) end widen_all_consts!(me.src) - if isa(me.bestguess, Const) - bg = me.bestguess::Const - const_ret = true - inferred_const = bg.val - elseif isconstType(me.bestguess, true) - const_ret = true - inferred_const = me.bestguess.parameters[1] - else - const_ret = false - inferred_const = nothing - end - - const_api = false - ispure = me.src.pure - inferred = me.src - if const_ret && inferred_const !== nothing + if isa(me.bestguess, Const) || isconstType(me.bestguess, true) + me.const_ret = true + ispure = me.src.pure if !ispure && length(me.src.code) < 10 ispure = true for stmt in me.src.code if !statement_effect_free(stmt, me.src, me.mod) - ispure = false; break + ispure = false + break end end if ispure for fl in me.src.slotflags if (fl & Slot_UsedUndef) != 0 - ispure = false; break + ispure = false + break end end end end + me.src.pure = ispure + + do_coverage = coverage_enabled() if ispure && !do_coverage # use constant calling convention - inferred = inferred_const # Do not emit `jlcall_api == 2` if coverage is enabled # so that we don't need to add coverage support # to the `jl_call_method_internal` fast path # Still set pure flag to make sure `inference` tests pass # and to possibly enable more optimization in the future - const_api = true + me.const_api = true + force_noinline || (me.src.inlineable = true) end - me.src.pure = ispure end # determine and cache inlineability if !me.src.inlineable && !force_noinline && isdefined(me.linfo, :def) - me.src.inlineable = const_api || isinlineable(me.linfo.def, me.src) + me.src.inlineable = isinlineable(me.linfo.def, me.src) end + me.src.inferred = true + nothing +end +# inference completed on `me` +# update the MethodInstance and notify the edges +function finish(me::InferenceState) + me.currpc = 1 # used by add_backedge if me.cached 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 - inferred = nothing + min_valid = me.min_valid + max_valid = me.max_valid + else + min_valid = UInt(0) + max_valid = UInt(0) + end + + # check if the existing me.linfo metadata is also sufficient to describe the current inference result + # to decide if it is worth caching it again (which would also clear any generated code) + already_inferred = false + if isdefined(me.linfo, :inferred) + inf = me.linfo.inferred + if !isa(inf, CodeInfo) || (inf::CodeInfo).inferred + if min_world(me.linfo) == min_valid && max_world(me.linfo) == max_valid + already_inferred = true + end + end + end + + if !already_inferred + const_flags = (me.const_ret) << 1 | me.const_api + if me.const_ret + if isa(me.bestguess, Const) + inferred_const = (me.bestguess::Const).val else - # compress code for non-toplevel thunks - inferred.code = ccall(:jl_compress_ast, Any, (Any, Any), me.linfo.def, inferred.code) + @assert isconstType(me.bestguess, true) + inferred_const = me.bestguess.parameters[1] + end + else + inferred_const = nothing + end + if me.const_api + # use constant calling convention + inferred_result = inferred_const + else + inferred_result = me.src + end + + if !toplevel + if !me.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 + inferred_result = nothing + else + # compress code for non-toplevel thunks + inferred_result.code = ccall(:jl_compress_ast, Any, (Any, Any), me.linfo.def, inferred_result.code) + end end end + cache = ccall(:jl_set_method_inferred, Ref{MethodInstance}, (Any, Any, Any, Any, Int32, UInt, UInt), + me.linfo, widenconst(me.bestguess), inferred_const, inferred_result, + const_flags, min_valid, max_valid) + if cache !== me.linfo + me.linfo.inInference = false + me.linfo = cache + end end - const_flags = (const_ret) << 1 | const_api - ccall(:jl_set_lambda_rettype, Void, (Any, Any, Int32, Any, Any), - me.linfo, widenconst(me.bestguess), const_flags, - inferred_const, inferred) end - me.src.inferred = true - me.linfo.inInference = false - # finalize and record the linfo result - me.inferred = true - # lazy-delete the item from active for several reasons: # efficiency, correctness, and recursion-safety nactive[] -= 1 active[findlast(active, me)] = nothing # update all of the callers by traversing the backedges - for (i,_) in me.backedges + for (i, _) in me.backedges if !me.fixedpoint || !i.fixedpoint # wake up each backedge, unless both me and it already reached a fixed-point (cycle resolution stage) delete!(i.edges, me) i.inworkq || push!(workq, i) i.inworkq = true end + add_backedge(me.linfo, i) end + + # finalize and record the linfo result + me.cached && (me.linfo.inInference = false) + me.inferred = true nothing end @@ -2579,8 +2785,9 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference function splitunion(atypes::Vector{Any}, i::Int) if i == 0 local sig = argtypes_to_type(atypes) - local li = ccall(:jl_get_spec_lambda, Any, (Any,), sig) + local li = ccall(:jl_get_spec_lambda, Any, (Any, UInt), sig, sv.params.world) li === nothing && return false + add_backedge(li, sv) local stmt = [] push!(stmt, Expr(:(=), linfo_var, li)) spec_hit === nothing && (spec_hit = genlabel(sv)) @@ -2623,7 +2830,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference append!(stmts, match) if error_label !== nothing push!(stmts, error_label) - push!(stmts, Expr(:call, GlobalRef(_topmod(sv.mod), :error), "error in type inference due to #265")) + push!(stmts, Expr(:call, GlobalRef(_topmod(sv.mod), :error), "fatal error in type inference (type bound)")) end local ret_var, merge if spec_miss !== nothing @@ -2646,8 +2853,9 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference return (ret_var, stmts) end else - local cache_linfo = ccall(:jl_get_spec_lambda, Any, (Any,), atype_unlimited) + local cache_linfo = ccall(:jl_get_spec_lambda, Any, (Any, UInt), atype_unlimited, sv.params.world) cache_linfo === nothing && return NF + add_backedge(cache_linfo, sv) e.head = :invoke unshift!(e.args, cache_linfo) return e @@ -2663,7 +2871,9 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference else atype = atype_unlimited end - meth = _methods_by_ftype(atype, 1) + min_valid = UInt[typemin(UInt)] + max_valid = UInt[typemax(UInt)] + meth = _methods_by_ftype(atype, 1, sv.params.world, min_valid, max_valid) if meth === false || length(meth) != 1 return invoke_NF() end @@ -2728,11 +2938,12 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end # see if the method has been previously inferred (and cached) - linfo = code_for_method(method, metharg, methsp, !force_infer) # Union{Void, MethodInstance} + linfo = code_for_method(method, metharg, methsp, sv.params.world, !force_infer) # Union{Void, MethodInstance} isa(linfo, MethodInstance) || return invoke_NF() linfo = linfo::MethodInstance if linfo.jlcall_api == 2 # in this case function can be inlined to a constant + add_backedge(linfo, sv) return inline_as_constant(linfo.inferred, argexprs, sv) end @@ -2753,9 +2964,12 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference frame.stmt_types[1][3] = VarState(atypes[3], false) typeinf_loop(frame) else - if isdefined(linfo, :inferred) + if isdefined(linfo, :inferred) && isa(linfo.inferred, CodeInfo) && (linfo.inferred::CodeInfo).inferred # use cache src = linfo.inferred + elseif linfo.inInference + # use WIP + frame = typeinf_active(linfo) elseif force_infer # create inferred code on-demand # but if we decided in the past not to try to infer this particular signature @@ -2768,12 +2982,21 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference # compute the return value if isa(frame, InferenceState) frame = frame::InferenceState - frame.inferred || return invoke_NF() linfo = frame.linfo src = frame.src - if linfo.jlcall_api == 2 - # in this case function can be inlined to a constant - return inline_as_constant(linfo.inferred, argexprs, sv) + if frame.const_api # handle like jlcall_api == 2 + if frame.inferred || !frame.cached + add_backedge(frame.linfo, sv) + else + add_backedge(frame, sv, 0) + end + if isa(frame.bestguess, Const) + inferred_const = (frame.bestguess::Const).val + else + @assert isconstType(frame.bestguess, true) + inferred_const = frame.bestguess.parameters[1] + end + return inline_as_constant(inferred_const, argexprs, sv) end rettype = widenconst(frame.bestguess) else @@ -2788,6 +3011,15 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference return invoke_NF() end + # 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, 0) + else + add_backedge(linfo, sv) + end + spvals = Any[] for i = 1:length(methsp) push!(spvals, methsp[i]) @@ -2906,7 +3138,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference lastexpr = pop!(body.args) if isa(lastexpr,LabelNode) push!(body.args, lastexpr) - push!(body.args, Expr(:call, GlobalRef(_topmod(sv.mod),:error), "fatal error in type inference")) + push!(body.args, Expr(:call, GlobalRef(_topmod(sv.mod), :error), "fatal error in type inference (lowering)")) lastexpr = nothing elseif !(isa(lastexpr,Expr) && lastexpr.head === :return) # code sometimes ends with a meta node, e.g. inbounds pop @@ -2947,13 +3179,14 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end - do_coverage = coverage_enabled() inlining_ignore = function (stmt::ANY) isa(stmt, Expr) && return is_meta_expr(stmt::Expr) isa(stmt, LineNumberNode) && return true stmt === nothing && return true return false end + + do_coverage = coverage_enabled() if do_coverage line = method.line if !isempty(stmts) && isa(stmts[1], LineNumberNode) @@ -4089,11 +4322,10 @@ function reindex_labels!(sv::InferenceState) end end -function return_type(f::ANY, t::ANY, params::InferenceParams=InferenceParams()) - # NOTE: if not processed by pure_eval_call during inference, a call to return_type - # might use difference InferenceParams than the method it is contained in... +function return_type(f::ANY, t::ANY) + params = InferenceParams(ccall(:jl_get_tls_world_age, UInt, ())) rt = Union{} - for m in _methods(f, t, -1) + for m in _methods(f, t, -1, params.world) ty = typeinf_type(m[3], m[1], m[2], true, params) ty === nothing && return Any rt = tmerge(rt, ty) @@ -4120,7 +4352,7 @@ let fs = Any[typeinf_ext, typeinf_loop, typeinf_edge, occurs_outside_getfield, e end end for f in fs - for m in _methods_by_ftype(Tuple{typeof(f), Vararg{Any}}, 10) + for m in _methods_by_ftype(Tuple{typeof(f), Vararg{Any}}, 10, typemax(UInt)) # remove any TypeVars from the intersection typ = Any[m[1].parameters...] for i = 1:length(typ) @@ -4128,7 +4360,7 @@ let fs = Any[typeinf_ext, typeinf_loop, typeinf_edge, occurs_outside_getfield, e typ[i] = typ[i].ub end end - typeinf_type(m[3], Tuple{typ...}, m[2], true, InferenceParams()) + typeinf_type(m[3], Tuple{typ...}, m[2], true, InferenceParams(typemax(UInt))) end end end diff --git a/base/methodshow.jl b/base/methodshow.jl index f00d8aa354760..2be02de975c56 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -58,9 +58,9 @@ function arg_decl_parts(m::Method) return tv, decls, file, line end -function kwarg_decl(sig::ANY, kwtype::DataType) - sig = Tuple{kwtype, Core.AnyVector, sig.parameters...} - kwli = ccall(:jl_methtable_lookup, Any, (Any, Any), kwtype.name.mt, sig) +function kwarg_decl(m::Method, kwtype::DataType) + sig = Tuple{kwtype, Core.AnyVector, m.sig.parameters...} + kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, max_world(m)) if kwli !== nothing kwli = kwli::Method src = kwli.isstaged ? kwli.unspecialized.inferred : kwli.source @@ -104,7 +104,7 @@ function show(io::IO, m::Method; kwtype::Nullable{DataType}=Nullable{DataType}() join(io, [isempty(d[2]) ? d[1] : d[1]*"::"*d[2] for d in decls[2:end]], ", ", ", ") if !isnull(kwtype) - kwargs = kwarg_decl(m.sig, get(kwtype)) + kwargs = kwarg_decl(m, get(kwtype)) if !isempty(kwargs) print(io, "; ") join(io, kwargs, ", ", ", ") @@ -227,7 +227,7 @@ function show(io::IO, ::MIME"text/html", m::Method; kwtype::Nullable{DataType}=N join(io, [isempty(d[2]) ? d[1] : d[1]*"::"*d[2]*"" for d in decls[2:end]], ", ", ", ") if !isnull(kwtype) - kwargs = kwarg_decl(m.sig, get(kwtype)) + kwargs = kwarg_decl(m, get(kwtype)) if !isempty(kwargs) print(io, "; ") join(io, kwargs, ", ", ", ") diff --git a/base/multi.jl b/base/multi.jl index a7d112df5608f..62063e50dfa36 100644 --- a/base/multi.jl +++ b/base/multi.jl @@ -124,10 +124,10 @@ for (idx, tname) in enumerate(msgtypes) end end -function deserialize_msg(s) +function deserialize_msg(s::AbstractSerializer) idx = read(s.io, UInt8) t = msgtypes[idx] - return deserialize_msg(s, t) + return eval(current_module(), Expr(:body, Expr(:return, Expr(:call, deserialize_msg, QuoteNode(s), QuoteNode(t))))) end function send_msg_unknown(s::IO, header, msg) @@ -321,7 +321,7 @@ function send_msg_(w::Worker, header, msg, now::Bool) try reset_state(w.w_serializer) serialize_hdr_raw(io, header) - serialize(w.w_serializer, msg) # io is wrapped in w_serializer + eval(current_module(), Expr(:body, Expr(:return, Expr(:call, serialize, QuoteNode(w.w_serializer), QuoteNode(msg))))) # io is wrapped in w_serializer write(io, MSG_BOUNDARY) if !now && w.gcflag @@ -812,7 +812,7 @@ function lookup_ref(pg, rrid, f) rv = get(pg.refs, rrid, false) if rv === false # first we've heard of this ref - rv = RemoteValue(f()) + rv = RemoteValue(eval(Main, Expr(:body, Expr(:return, Expr(:call, f))))) pg.refs[rrid] = rv push!(rv.clientset, rrid.whence) end diff --git a/base/reflection.jl b/base/reflection.jl index 6dda825683c6a..ebcbea5e686bf 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -342,7 +342,7 @@ tt_cons(t::ANY, tup::ANY) = (@_pure_meta; Tuple{t, (isa(tup, Type) ? tup.paramet Returns an array of lowered ASTs for the methods matching the given generic function and type signature. """ -function code_lowered(f, t::ANY=Tuple) +function code_lowered(f::ANY, t::ANY=Tuple) asts = map(methods(f, t)) do m m = m::Method return uncompressed_ast(m, m.isstaged ? m.unspecialized.inferred : m.source) @@ -352,13 +352,16 @@ end # low-level method lookup functions used by the compiler -function _methods(f::ANY,t::ANY,lim) +function _methods(f::ANY, t::ANY, lim::Int, world::UInt) ft = isa(f,Type) ? Type{f} : typeof(f) tt = isa(t,Type) ? Tuple{ft, t.parameters...} : Tuple{ft, t...} - return _methods_by_ftype(tt, lim) + return _methods_by_ftype(tt, lim, world) end -function _methods_by_ftype(t::ANY, lim) +function _methods_by_ftype(t::ANY, lim::Int, world::UInt) + return _methods_by_ftype(t, lim, world, UInt[typemin(UInt)], UInt[typemax(UInt)]) +end +function _methods_by_ftype(t::ANY, lim::Int, world::UInt, min::Array{UInt,1}, max::Array{UInt,1}) tp = t.parameters::SimpleVector nu = 1 for ti in tp @@ -367,15 +370,16 @@ function _methods_by_ftype(t::ANY, lim) end end if 1 < nu <= 64 - return _methods(Any[tp...], length(tp), lim, []) + return _methods_by_ftype(Any[tp...], length(tp), lim, [], world, min, max) end # XXX: the following can return incorrect answers that the above branch would have corrected - return ccall(:jl_matching_methods, Any, (Any,Cint,Cint), t, lim, 0) + return ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), t, lim, 0, world, min, max) end -function _methods(t::Array,i,lim::Integer,matching::Array{Any,1}) +function _methods_by_ftype(t::Array, i, lim::Integer, matching::Array{Any,1}, world::UInt, min::Array{UInt,1}, max::Array{UInt,1}) if i == 0 - new = ccall(:jl_matching_methods, Any, (Any,Cint,Cint), Tuple{t...}, lim, 0) + world = typemax(UInt) + new = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), Tuple{t...}, lim, 0, world, min, max) new === false && return false append!(matching, new::Array{Any,1}) else @@ -383,14 +387,14 @@ function _methods(t::Array,i,lim::Integer,matching::Array{Any,1}) if isa(ti, Union) for ty in (ti::Union).types t[i] = ty - if _methods(t,i-1,lim,matching) === false + if _methods_by_ftype(t, i - 1, lim, matching, world, min, max) === false t[i] = ti return false end end t[i] = ti else - return _methods(t,i-1,lim,matching) + return _methods_by_ftype(t, i - 1, lim, matching, world, min, max) end end return matching @@ -430,7 +434,8 @@ function methods(f::ANY, t::ANY) throw(ArgumentError("argument is not a generic function")) end t = to_tuple_type(t) - return MethodList(Method[m[3] for m in _methods(f,t,-1)], typeof(f).name.mt) + world = typemax(UInt) + return MethodList(Method[m[3] for m in _methods(f, t, -1, world)], typeof(f).name.mt) end methods(f::Core.Builtin) = MethodList(Method[], typeof(f).name.mt) @@ -438,7 +443,10 @@ methods(f::Core.Builtin) = MethodList(Method[], typeof(f).name.mt) function methods_including_ambiguous(f::ANY, t::ANY) ft = isa(f,Type) ? Type{f} : typeof(f) tt = isa(t,Type) ? Tuple{ft, t.parameters...} : Tuple{ft, t...} - ms = ccall(:jl_matching_methods, Any, (Any,Cint,Cint), tt, -1, 1)::Array{Any,1} + world = typemax(UInt) + min = UInt[typemin(UInt)] + max = UInt[typemax(UInt)] + ms = ccall(:jl_matching_methods, Any, (Any, Cint, Cint, UInt, Ptr{UInt}, Ptr{UInt}), tt, -1, 1, world, min, max)::Array{Any,1} return MethodList(Method[m[3] for m in ms], typeof(f).name.mt) end function methods(f::ANY) @@ -523,6 +531,7 @@ function _dump_function(f::ANY, t::ANY, native::Bool, wrapper::Bool, throw(ArgumentError("argument is not a generic function")) end # get the MethodInstance for the method match + world = typemax(UInt) meth = which(f, t) t = to_tuple_type(t) ft = isa(f, Type) ? Type{f} : typeof(f) @@ -530,21 +539,21 @@ function _dump_function(f::ANY, t::ANY, native::Bool, wrapper::Bool, (ti, env) = ccall(:jl_match_method, Any, (Any, Any, Any), tt, meth.sig, meth.tvars)::SimpleVector meth = func_for_method_checked(meth, tt) - linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance}, (Any, Any, Any), meth, tt, env) + linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance}, (Any, Any, Any, UInt), meth, tt, env, world) # get the code for it - return _dump_function(linfo, native, wrapper, strip_ir_metadata, dump_module, syntax, optimize, params) + return _dump_function_linfo(linfo, world, native, wrapper, strip_ir_metadata, dump_module, syntax, optimize, params) end -function _dump_function(linfo::Core.MethodInstance, native::Bool, wrapper::Bool, - strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol=:att, - optimize::Bool=true, params::CodegenParams=CodegenParams()) +function _dump_function_linfo(linfo::Core.MethodInstance, world::UInt, native::Bool, wrapper::Bool, + strip_ir_metadata::Bool, dump_module::Bool, syntax::Symbol=:att, + optimize::Bool=true, params::CodegenParams=CodegenParams()) if syntax != :att && syntax != :intel throw(ArgumentError("'syntax' must be either :intel or :att")) end if native - llvmf = ccall(:jl_get_llvmf_decl, Ptr{Void}, (Any, Bool, CodegenParams), linfo, wrapper, params) + llvmf = ccall(:jl_get_llvmf_decl, Ptr{Void}, (Any, UInt, Bool, CodegenParams), linfo, world, wrapper, params) else - llvmf = ccall(:jl_get_llvmf_defn, Ptr{Void}, (Any, Bool, Bool, CodegenParams), linfo, wrapper, optimize, params) + llvmf = ccall(:jl_get_llvmf_defn, Ptr{Void}, (Any, UInt, Bool, Bool, CodegenParams), linfo, world, wrapper, optimize, params) end if llvmf == C_NULL error("could not compile the specified method") @@ -612,10 +621,11 @@ function code_typed(f::ANY, types::ANY=Tuple; optimize=true) end types = to_tuple_type(types) asts = [] - params = Core.Inference.InferenceParams() - for x in _methods(f, types, -1) + world = typemax(UInt) + params = Core.Inference.InferenceParams(world) + for x in _methods(f, types, -1, world) meth = func_for_method_checked(x[3], types) - (code, ty) = Core.Inference.typeinf_code(meth, x[1], x[2], optimize, optimize, params) + (_, code, ty) = Core.Inference.typeinf_code(meth, x[1], x[2], optimize, optimize, params) code === nothing && error("inference not successful") # Inference disabled? push!(asts, uncompressed_ast(meth, code) => ty) end @@ -629,8 +639,9 @@ function return_types(f::ANY, types::ANY=Tuple) end types = to_tuple_type(types) rt = [] - params = Core.Inference.InferenceParams() - for x in _methods(f, types, -1) + world = typemax(UInt) + params = Core.Inference.InferenceParams(world) + for x in _methods(f, types, -1, world) meth = func_for_method_checked(x[3], types) ty = Core.Inference.typeinf_type(meth, x[1], x[2], true, params) ty === nothing && error("inference not successful") # Inference disabled? @@ -659,7 +670,7 @@ function which(f::ANY, t::ANY) else ft = isa(f,Type) ? Type{f} : typeof(f) tt = Tuple{ft, t.parameters...} - m = ccall(:jl_gf_invoke_lookup, Any, (Any,), tt) + m = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tt, typemax(UInt)) if m === nothing error("no method found for the specified argument types") end @@ -765,13 +776,14 @@ true function method_exists(f::ANY, t::ANY) t = to_tuple_type(t) t = Tuple{isa(f,Type) ? Type{f} : typeof(f), t.parameters...} - return ccall(:jl_method_exists, Cint, (Any, Any), typeof(f).name.mt, t) != 0 + return ccall(:jl_method_exists, Cint, (Any, Any, UInt), typeof(f).name.mt, t, + typemax(UInt)) != 0 end function isambiguous(m1::Method, m2::Method) ti = typeintersect(m1.sig, m2.sig) ti === Bottom && return false - ml = _methods_by_ftype(ti, -1) + ml = _methods_by_ftype(ti, -1, typemax(UInt)) isempty(ml) && return true for m in ml if ti <: m[3].sig @@ -780,3 +792,8 @@ function isambiguous(m1::Method, m2::Method) end return true end + +min_world(m::Method) = reinterpret(UInt, m.min_world) +max_world(m::Method) = reinterpret(UInt, m.max_world) +min_world(m::Core.MethodInstance) = reinterpret(UInt, m.min_world) +max_world(m::Core.MethodInstance) = reinterpret(UInt, m.max_world) diff --git a/base/replutil.jl b/base/replutil.jl index 0a5ddaed2d24a..e4061ae080c5e 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -335,9 +335,14 @@ function showerror(io::IO, ex::MethodError) basef = getfield(Base, name) if basef !== ex.f && method_exists(basef, arg_types) println(io) - print(io, "you may have intended to import Base.", name) + print(io, "You may have intended to import Base.", name) end end + if method_exists(ex.f, arg_types) + curworld = ccall(:jl_get_world_counter, UInt, ()) + println(io) + print(io, "The applicable method may be too new: running in world age $(ex.world), while current world is $(curworld).") + end if !is_arg_types # Check for row vectors used where a column vector is intended. vec_args = [] @@ -514,7 +519,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[]) kwords = Symbol[] if isdefined(ft.name.mt, :kwsorter) kwsorter_t = typeof(ft.name.mt.kwsorter) - kwords = kwarg_decl(method.sig, kwsorter_t) + kwords = kwarg_decl(method, kwsorter_t) length(kwords) > 0 && print(buf, "; ", join(kwords, ", ")) end print(buf, ")") @@ -535,6 +540,13 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[]) end end end + if ex.world < min_world(method) + print(buf, " (method too new to be called from this world context.)") + end + if ex.world > max_world(method) + print(buf, " (method deleted before this world age.)") + end + # TODO: indicate if it's in the wrong world push!(lines, (buf, right_matches)) end end diff --git a/base/serialize.jl b/base/serialize.jl index d3b384cb5c059..fb662009658fd 100644 --- a/base/serialize.jl +++ b/base/serialize.jl @@ -416,7 +416,7 @@ function serialize_typename(s::AbstractSerializer, t::TypeName) serialize(s, t.primary.ninitialized) if isdefined(t, :mt) serialize(s, t.mt.name) - serialize(s, t.mt.defs) + serialize(s, collect(Base.MethodList(t.mt))) serialize(s, t.mt.max_args) if isdefined(t.mt, :kwsorter) serialize(s, t.mt.kwsorter) @@ -621,7 +621,7 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) name = deserialize(s)::Symbol file = deserialize(s)::Symbol line = deserialize(s)::Int32 - sig = deserialize(s) + sig = deserialize(s)::DataType tvars = deserialize(s)::Union{SimpleVector, TypeVar} sparam_syms = deserialize(s)::SimpleVector ambig = deserialize(s)::Union{Array{Any,1}, Void} @@ -650,6 +650,10 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) else meth.source = template end + ftype = ccall(:jl_first_argument_datatype, Any, (Any,), sig)::DataType + if isdefined(ftype.name, :mt) && nothing === ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), ftype.name.mt, sig, typemax(UInt)) + ccall(:jl_method_table_insert, Void, (Any, Any, Ptr{Void}), ftype.name.mt, meth, C_NULL) + end known_object_data[lnumber] = meth end return meth @@ -803,8 +807,12 @@ function deserialize_typename(s::AbstractSerializer, number) if makenew tn.mt = ccall(:jl_new_method_table, Any, (Any, Any), name, tn.module) tn.mt.name = mtname - tn.mt.defs = defs tn.mt.max_args = maxa + for def in defs + if isdefined(def, :sig) + ccall(:jl_method_table_insert, Void, (Any, Any, Ptr{Void}), tn.mt, def, C_NULL) + end + end end tag = Int32(read(s.io, UInt8)::UInt8) if tag != UNDEFREF_TAG diff --git a/base/sparse/sparsematrix.jl b/base/sparse/sparsematrix.jl index da0a3be768008..5318314fe7e12 100644 --- a/base/sparse/sparsematrix.jl +++ b/base/sparse/sparsematrix.jl @@ -2253,8 +2253,8 @@ for (Bsig, A1sig, A2sig, gbb, funcname) in global $funcname function $funcname(f::Function, B::$Bsig, A1::$A1sig, A2::$A2sig) func = @get! cache f gen_broadcast_function_sparse($gbb, f, ($A1sig) <: SparseMatrixCSC) - func(B, A1, A2) - B + eval(Expr(:call, () -> func(B, A1, A2))) # need eval because func was just created by gen_broadcast_function_sparse + return B end end # let broadcast_cache end diff --git a/base/sysimg.jl b/base/sysimg.jl index 915d9eff063ae..af90f85eceeae 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -70,12 +70,6 @@ convert{T<:VecElement}(::Type{T}, arg::T) = arg include("checked.jl") importall .Checked -# Symbol constructors -if !isdefined(Core, :Inference) - Symbol(s::String) = Symbol(s.data) - Symbol(a::Array{UInt8,1}) = - ccall(:jl_symbol_n, Ref{Symbol}, (Ptr{UInt8}, Int32), a, length(a)) -end # vararg Symbol constructor Symbol(x...) = Symbol(string(x...)) diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 68d40c9e226ae..ea6662d448bdc 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -64,6 +64,7 @@ The following data types exist in lowered form: Marks a point where a variable is created. This has the effect of resetting a variable to undefined. + ### Expr types These symbols appear in the `head` field of `Expr`s in lowered form. @@ -192,9 +193,10 @@ These symbols appear in the `head` field of `Expr`s in lowered form. * `:pop_loc`: returns to the source location before the matching `:push_loc`. + ### Method -A unique'd container describing the shared metadata for a single (unspecialized) method. +A unique'd container describing the shared metadata for a single method. * `name`, `module`, `file`, `line`, `sig` @@ -223,6 +225,11 @@ A unique'd container describing the shared metadata for a single (unspecialized) Descriptive bit-fields for the source code of this Method. + * `min_world` / `max_world` + + The range of world ages for which this method is visible to dispatch. + + ### MethodInstance A unique'd container describing a single callable signature for a Method. See especially [Proper maintenance and care of multi-threading locks](@ref) @@ -264,9 +271,16 @@ for important details on how to modify these fields safely. The ABI to use when calling `fptr`. Some significant ones include: - * 0 - not compiled yet + * 0 - Not compiled yet. * 1 - JL_CALLABLE `jl_value_t *(*)(jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` - * 2 - constant (stored in `inferred`) + * 2 - Constant (value stored in `inferred`) + * 3 - With Static-parameters forwarded `jl_value_t *(*)(jl_svec_t *sparams, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` + * 4 - Run in interpreter `jl_value_t *(*)(jl_method_instance_t *meth, jl_function_t *f, jl_value_t *args[nargs], uint32_t nargs)` + + * `min_world` / `max_world` + + The range of world ages for which this method instance is valid to be called. + ### CodeInfo @@ -320,6 +334,7 @@ Boolean properties: Whether this is known to be a pure function of its arguments, without respect to the state of the method caches or other mutable global state. + ## Surface syntax AST Front end ASTs consist entirely of `Expr`s and atoms (e.g. symbols, numbers). There is generally diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index 27f1511819d1e..c45d11289b7ad 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -921,6 +921,53 @@ true for macros too - and just like for macros, the use of [`eval()`](@ref) in a is a sign that you're doing something the wrong way.) However, unlike macros, the runtime system cannot correctly handle a call to [`eval()`](@ref), so it is disallowed. +It is also important to see how `@generated` functions interact with method redefinition. +Following the principle that a correct `@generated` function must not observe any +mutable state or cause any mutation of global state, we see the following behavior. +Observe that the generated function *cannot* call any method that was not defined +prior to the *definition* of the generated function itself. + +```julia +julia> # initially f(x) has one definition: + +julia> f(x) = "original definition"; + +julia> # start some other operations that use f(x): + +julia> g(x) = f(x); + +julia> @generated gen1(x) = f(x); + +julia> @generated gen2(x) = :(f(x)); + +julia> # now we add some new definitions for f(x): + +julia> f(x::Int) = "definition for Int"; + +julia> f(x::Type{Int}) = "definition for Type{Int}"; + +julia> # and compare how these results differ: + +julia> f(1) +"definition for Int" + +julia> g(1) +"definition for Int" + +julia> gen1(1) +"original definition" + +julia> gen2(1) +"definition for Int" + +julia> # each method of a generated function has its own view of defined functions: + +julia> @generated gen1(x::Real) = f(x); + +julia> gen1(1) +"definition for Type{Int}" +``` + The example generated function `foo` above did not do anything a normal function `foo(x) = x * x` could not do (except printing the type on the first invocation, and incurring higher overhead). However, the power of a generated function lies in its ability to compute different quoted expressions diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index cb43659dac4c2..24a7ef9ac85db 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -389,6 +389,114 @@ false The `same_type_numeric` function behaves much like the `same_type` function defined above, but is only defined for pairs of numbers. +Redefining Methods +------------------ + +When redefining a method or adding new methods, +it is important to realize that these changes don't take effect immediately. +This is key to Julia's ability to statically infer and compile code to run fast, +without the usual JIT tricks and overhead. +Indeed, any new method definition won't be visible to the current runtime environment, +including Tasks and Threads (and any previously defined `@generated` functions). +Let's start with an example to see what this means: + +```julia +julia> function tryeval() + @eval newfun() = 1 + newfun() + end +tryeval (generic function with 1 method) + +julia> tryeval() +ERROR: MethodError: no method matching newfun() +The applicable method may be too new: running in world age xxxx1, while current world is xxxx2. +Closest candidates are: + newfun() at none:1 (method too new to be called from this world context.) + in tryeval() at none:1 + ... + +julia> newfun() +1 +``` + +In this example, observe that the new definition for `newfun` has been created, +but can't be immediately called. +The new global is immediately visible to the `tryeval` function, +so you could write `return newfun` (without parentheses). +But neither you, nor any of your callers, nor the functions they call, or etc. +can call this new method definition! + +But there's an exception: future calls to `newfun` *from the REPL* work as expected, +being able to both see and call the new definition of `newfun`. + +However, future calls to `tryeval` will continue to see the definition of `newfun` as it was +*at the previous statement at the REPL*, and thus before that call to `tryeval`. + +You may want to try this for yourself to see how it works. + +The implementation of this behavior is a "world age counter". +This monotonically increasing value tracks each method definition operation. +This allows describing "the set of method definitions visible to a given runtime environment" +as a single number, or "world age". +It also allows comparing the methods available in two worlds just by comparing their ordinal value. +In the example above, we see that the "current world" (in which the method `newfun()` exists), +is one greater than the task-local "runtime world" that was fixed when the execution of `tryeval` started. + +Sometimes it is necessary to get around this (for example, if you are implementing the above REPL). +Well, don't despair, since there's an easy solution: just call `eval` a second time. +For example, here we create a zero-argument closure over `ans` and `eval` a call to it: + +```julia + julia> function tryeval2() + ans = (@eval newfun2() = 1) + res = eval(Expr(:call, + function() + return ans() + 1 + end)) + return res + end + tryeval2 (generic function with 1 method) + + julia> tryeval2() + 2 +``` + +Finally, let's take a look at some more complex examples where this rule comes into play. + +```julia + julia> # initially f(x) has one definition: + + julia> f(x) = "original definition"; + + julia> # start some other operations that use f(x): + + julia> g(x) = f(x); + + julia> t = @async f(wait()); yield(); + + julia> # now we add some new definitions for f(x): + + julia> f(x::Int) = "definition for Int"; + + julia> f(x::Type{Int}) = "definition for Type{Int}"; + + julia> # and compare how these results differ: + + julia> f(1) + "definition for Int" + + julia> g(1) + "definition for Int" + + julia> wait(schedule(t, 1)) + "original definition" + + julia> t = @async f(wait()); yield(); + + julia> wait(schedule(t, 1)) + "definition for Int" +``` + ## Parametrically-constrained Varargs methods Function parameters can also be used to constrain the number of arguments that may be supplied diff --git a/src/alloc.c b/src/alloc.c index a768ea66243d4..acec1df4b79fc 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -409,6 +409,7 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) li->inferred_const = NULL; li->rettype = (jl_value_t*)jl_any_type; li->sparam_vals = jl_emptysvec; + li->backedges = NULL; li->fptr = NULL; li->unspecialized_ducttape = NULL; li->jlcall_api = 0; @@ -418,6 +419,8 @@ JL_DLLEXPORT jl_method_instance_t *jl_new_method_instance_uninit(void) li->specTypes = NULL; li->inInference = 0; li->def = NULL; + li->min_world = 0; + li->max_world = 0; return li; } @@ -457,8 +460,9 @@ STATIC_INLINE jl_value_t *jl_call_staged(jl_svec_t *sparam_vals, jl_method_insta fptr.fptr = generator->fptr; fptr.jlcall_api = generator->jlcall_api; if (__unlikely(fptr.fptr == NULL || fptr.jlcall_api == 0)) { - void *F = jl_compile_linfo(generator, (jl_code_info_t*)generator->inferred, &jl_default_cgparams).functionObject; - fptr = jl_generate_fptr(generator, F); + size_t world = generator->def->min_world; + void *F = jl_compile_linfo(&generator, (jl_code_info_t*)generator->inferred, world, &jl_default_cgparams).functionObject; + fptr = jl_generate_fptr(generator, F, world); } assert(jl_svec_len(generator->def->sparam_syms) == jl_svec_len(sparam_vals)); if (fptr.jlcall_api == 1) @@ -515,6 +519,7 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) // invoke code generator assert(jl_nparams(tt) == jl_array_len(argnames) || (linfo->def->isva && (jl_nparams(tt) >= jl_array_len(argnames) - 1))); + // TODO: set world to that of the generator while calling func jl_array_ptr_set(body->args, 1, jl_call_staged(sparam_vals, generator, jl_svec_data(tt->parameters), jl_nparams(tt))); @@ -574,10 +579,12 @@ jl_method_instance_t *jl_get_specialized(jl_method_t *m, jl_tupletype_t *types, new_linfo->def = m; new_linfo->specTypes = types; new_linfo->sparam_vals = sp; + new_linfo->min_world = m->min_world; + new_linfo->max_world = m->max_world; return new_linfo; } -JL_DLLEXPORT void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) +static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) { uint8_t j; uint8_t called = 0; @@ -642,6 +649,8 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(void) m->nargs = 0; m->needs_sparam_vals_ducttape = 2; m->traced = 0; + m->min_world = 1; + m->max_world = ~(size_t)0; JL_MUTEX_INIT(&m->writelock); return m; } @@ -663,6 +672,7 @@ jl_method_t *jl_new_method(jl_code_info_t *definition, JL_GC_PUSH1(&root); jl_method_t *m = jl_new_method_uninit(); + m->min_world = ++jl_world_counter; m->isstaged = isstaged; m->name = name; m->sig = sig; @@ -772,7 +782,7 @@ JL_DLLEXPORT jl_sym_t *jl_symbol_lookup(const char *str) return symtab_lookup(&symtab, str, strlen(str), NULL); } -JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, int32_t len) +JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, size_t len) { if (memchr(str, 0, len)) jl_exceptionf(jl_argumenterror_type, "Symbol name may not contain \\0"); @@ -844,6 +854,7 @@ JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *mo mt->cache.unknown = jl_nothing; mt->max_args = 0; mt->kwsorter = NULL; + mt->backedges = NULL; JL_MUTEX_INIT(&mt->writelock); return mt; } @@ -862,9 +873,6 @@ JL_DLLEXPORT jl_typename_t *jl_new_typename_in(jl_sym_t *name, jl_module_t *modu tn->names = NULL; tn->hash = bitmix(bitmix(module ? module->uuid : 0, name->hash), 0xa1ada1da); tn->mt = NULL; - JL_GC_PUSH1(&tn); - tn->mt = NULL; - JL_GC_POP(); return tn; } diff --git a/src/ast.c b/src/ast.c index d25a82759d6b9..531a8879bb52b 100644 --- a/src/ast.c +++ b/src/ast.c @@ -153,14 +153,15 @@ value_t fl_invoke_julia_macro(fl_context_t *fl_ctx, value_t *args, uint32_t narg int i; for(i=1; i < nargs; i++) margs[i] = scm_to_julia(fl_ctx, args[i], 1); jl_value_t *result = NULL; + size_t world = jl_get_ptls_states()->world_age; JL_TRY { margs[0] = scm_to_julia(fl_ctx, args[0], 1); margs[0] = jl_toplevel_eval(margs[0]); - mfunc = jl_method_lookup(jl_gf_mtable(margs[0]), margs, nargs, 1); + mfunc = jl_method_lookup(jl_gf_mtable(margs[0]), margs, nargs, 1, world); if (mfunc == NULL) { JL_GC_POP(); - jl_method_error((jl_function_t*)margs[0], margs, nargs); + jl_method_error((jl_function_t*)margs[0], margs, nargs, world); // unreachable } margs[nargs] = result = jl_call_method_internal(mfunc, margs, nargs); @@ -720,6 +721,7 @@ jl_value_t *jl_parse_eval_all(const char *fname, int last_lineno = jl_lineno; const char *last_filename = jl_filename; + size_t last_age = jl_get_ptls_states()->world_age; jl_lineno = 0; jl_filename = fname; jl_array_t *roots = NULL; @@ -737,10 +739,12 @@ jl_value_t *jl_parse_eval_all(const char *fname, JL_TIMING(LOWERING); expansion = fl_applyn(fl_ctx, 1, symbol_value(symbol(fl_ctx, "jl-expand-to-thunk")), car_(ast)); } + jl_get_ptls_states()->world_age = jl_world_counter; form = scm_to_julia(fl_ctx, expansion, 0); jl_sym_t *head = NULL; if (jl_is_expr(form)) head = ((jl_expr_t*)form)->head; JL_SIGATOMIC_END(); + jl_get_ptls_states()->world_age = jl_world_counter; if (head == jl_incomplete_sym) jl_errorf("syntax: %s", jl_string_data(jl_exprarg(form,0))); else if (head == error_sym) @@ -760,6 +764,7 @@ jl_value_t *jl_parse_eval_all(const char *fname, result = jl_box_long(jl_lineno); err = 1; } + jl_get_ptls_states()->world_age = last_age; jl_lineno = last_lineno; jl_filename = last_filename; fl_free_gc_handles(fl_ctx, 1); diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 695916e7fdc15..0cd436157fc7d 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -22,7 +22,7 @@ extern "C" { DECLARE_BUILTIN(throw); DECLARE_BUILTIN(is); DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(sizeof); DECLARE_BUILTIN(issubtype); DECLARE_BUILTIN(isa); -DECLARE_BUILTIN(typeassert); DECLARE_BUILTIN(_apply); +DECLARE_BUILTIN(_apply); DECLARE_BUILTIN(_apply_pure); DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(nfields); DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(svec); DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(setfield); @@ -30,6 +30,7 @@ DECLARE_BUILTIN(fieldtype); DECLARE_BUILTIN(arrayref); DECLARE_BUILTIN(arrayset); DECLARE_BUILTIN(arraysize); DECLARE_BUILTIN(apply_type); DECLARE_BUILTIN(applicable); DECLARE_BUILTIN(invoke); DECLARE_BUILTIN(_expr); +DECLARE_BUILTIN(typeassert); #ifdef __cplusplus } diff --git a/src/builtins.c b/src/builtins.c index faa6d87df2156..721e6270d21ca 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -219,6 +219,7 @@ JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) #endif eh->defer_signal = ptls->defer_signal; eh->finalizers_inhibited = ptls->finalizers_inhibited; + eh->world_age = ptls->world_age; current_task->eh = eh; #ifdef ENABLE_TIMINGS eh->timing_stack = current_task->timing_stack; @@ -549,6 +550,32 @@ JL_CALLABLE(jl_f__apply) return result; } +// this is like `_apply`, but with quasi-exact checks to make sure it is pure +JL_CALLABLE(jl_f__apply_pure) +{ + jl_ptls_t ptls = jl_get_ptls_states(); + int last_in = ptls->in_pure_callback; + jl_value_t *ret = NULL; + JL_TRY { + ptls->in_pure_callback = 1; + // because this function was declared pure, + // we should be allowed to run it in any world + // so we run it in the newest world; + // because, why not :) + // and `promote` works better this way + size_t last_age = ptls->world_age; + ptls->world_age = jl_world_counter; + ret = jl_f__apply(NULL, args, nargs); + ptls->world_age = last_age; + ptls->in_pure_callback = last_in; + } + JL_CATCH { + ptls->in_pure_callback = last_in; + jl_rethrow(); + } + return ret; +} + // eval ----------------------------------------------------------------------- JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) @@ -562,6 +589,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) jl_error("eval cannot be used in a generated function"); jl_value_t *v = NULL; int last_lineno = jl_lineno; + size_t last_age = ptls->world_age; jl_module_t *last_m = ptls->current_module; jl_module_t *task_last_m = ptls->current_task->current_module; if (jl_options.incremental && jl_generating_output()) { @@ -574,6 +602,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) } JL_TRY { ptls->current_task->current_module = ptls->current_module = m; + ptls->world_age = jl_world_counter; v = jl_toplevel_eval(ex); } JL_CATCH { @@ -583,6 +612,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) jl_rethrow(); } jl_lineno = last_lineno; + ptls->world_age = last_age; ptls->current_module = last_m; ptls->current_task->current_module = task_last_m; assert(v); @@ -980,7 +1010,8 @@ static void jl_check_type_tuple(jl_value_t *t, jl_sym_t *name, const char *ctx) JL_CALLABLE(jl_f_applicable) { JL_NARGSV(applicable, 1); - return jl_method_lookup(jl_gf_mtable(args[0]), args, nargs, 1) != NULL ? + size_t world = jl_get_ptls_states()->world_age; + return jl_method_lookup(jl_gf_mtable(args[0]), args, nargs, 1, world) != NULL ? jl_true : jl_false; } @@ -1142,6 +1173,7 @@ void jl_init_primitives(void) // internal functions add_builtin_func("apply_type", jl_f_apply_type); add_builtin_func("_apply", jl_f__apply); + add_builtin_func("_apply_pure", jl_f__apply_pure); add_builtin_func("_expr", jl_f__expr); add_builtin_func("svec", jl_f_svec); diff --git a/src/ccall.cpp b/src/ccall.cpp index b3e85fe2cd26d..530e1897d32d8 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -689,7 +689,10 @@ static jl_value_t* try_eval(jl_value_t *ex, jl_codectx_t *ctx, const char *failu if (constant || jl_is_ssavalue(ex)) return constant; JL_TRY { + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = ctx->world; constant = jl_interpret_toplevel_expr_in(ctx->module, ex, ctx->source, ctx->linfo->sparam_vals); + jl_get_ptls_states()->world_age = last_age; } JL_CATCH { if (compiletime) diff --git a/src/codegen.cpp b/src/codegen.cpp index c972b68aba6e9..a7464aae6c2fb 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -403,6 +403,7 @@ Function *juliapersonality_func; #endif static Function *diff_gc_total_bytes_func; static Function *jlarray_data_owner_func; +static Function *jlgetworld_func; // placeholder functions static Function *gcroot_func; @@ -541,6 +542,7 @@ struct jl_codectx_t { jl_method_instance_t *linfo; jl_code_info_t *source; jl_array_t *code; + size_t world; const char *name; StringRef file; ssize_t *line; @@ -556,6 +558,7 @@ struct jl_codectx_t { CallInst *ptlsStates; Value *signalPage; + Value *world_age_field; bool debug_enabled; bool is_inbounds{false}; @@ -821,7 +824,12 @@ void jl_dump_compiles(void *s) // --- entry point --- //static int n_emit=0; -static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_info_t *src, jl_llvm_functions_t *declarations, const jl_cgparams_t *params); +static std::unique_ptr emit_function( + jl_method_instance_t *lam, + jl_code_info_t *src, + size_t world, + jl_llvm_functions_t *declarations, + const jl_cgparams_t *params); void jl_add_linfo_in_flight(StringRef name, jl_method_instance_t *linfo, const DataLayout &DL); // this generates llvm code for the lambda info @@ -829,23 +837,35 @@ void jl_add_linfo_in_flight(StringRef name, jl_method_instance_t *linfo, const D // (and the shadow module), but doesn't yet compile // or generate object code for it extern "C" -jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *src, const jl_cgparams_t *params) +jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t *src, size_t world, const jl_cgparams_t *params) { JL_TIMING(CODEGEN); + jl_method_instance_t *li = *pli; assert(jl_is_method_instance(li)); jl_llvm_functions_t decls = {}; // Step 1. See if it is already compiled, // Get the codegen lock, // And get the source - if (!src) { + if (li->def == NULL) { + JL_LOCK(&codegen_lock); + src = (jl_code_info_t*)li->inferred; + decls = li->functionObjectsDecls; + if (decls.functionObject != NULL || !src || !jl_is_code_info(src) || li->jlcall_api == 2) { + JL_UNLOCK(&codegen_lock); + return decls; + } + } + else if (!src) { // Step 1a. If the caller didn't provide the source, // try to infer it for ourself // first see if it is already compiled decls = li->functionObjectsDecls; if ((params->cached && decls.functionObject != NULL) || li->jlcall_api == 2) { - return decls; + if (li->min_world <= world && li->max_world >= world) + return decls; } JL_LOCK(&codegen_lock); + assert(li->min_world <= world && li->max_world >= world); decls = li->functionObjectsDecls; if ((params->cached && decls.functionObject != NULL) || li->jlcall_api == 2) { JL_UNLOCK(&codegen_lock); @@ -856,7 +876,8 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *s src = (jl_code_info_t*)li->inferred; if (src) { if (!jl_is_code_info(src)) { - src = jl_type_infer(li, 0); + src = jl_type_infer(pli, world, 0); + li = *pli; } if (!src || li->jlcall_api == 2) { JL_UNLOCK(&codegen_lock); @@ -864,7 +885,7 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *s } } else { - // failed to compile + // declare a failure to compile JL_UNLOCK(&codegen_lock); return decls; } @@ -896,10 +917,19 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *s // Step 3. actually do the work of emitting the function std::unique_ptr m; - Function *f = (Function*)decls.functionObject, *specf = (Function*)decls.specFunctionObject; JL_TRY { - m = emit_function(li, src, &li->functionObjectsDecls, params); - decls = li->functionObjectsDecls; + jl_llvm_functions_t *pdecls; + if (!params->cached) + pdecls = &decls; + else if (li->min_world <= world && li->max_world >= world) + pdecls = &li->functionObjectsDecls; + else if (li->def == NULL) + pdecls = &li->functionObjectsDecls; + else + pdecls = &decls; + m = emit_function(li, src, world, pdecls, params); + if (world) + decls = li->functionObjectsDecls; //n_emit++; } JL_CATCH { @@ -913,12 +943,8 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *s JL_UNLOCK(&codegen_lock); // Might GC jl_rethrow_with_add("error compiling %s", jl_symbol_name(li->def ? li->def->name : anonymous_sym)); } - if (!params->cached) { - li->functionObjectsDecls.functionObject = f; - li->functionObjectsDecls.specFunctionObject = specf; - } - f = (Function*)decls.functionObject; - specf = (Function*)decls.specFunctionObject; + Function *f = (Function*)decls.functionObject; + Function *specf = (Function*)decls.specFunctionObject; // Step 4. Prepare debug info to receive this function // record that this function name came from this linfo, @@ -940,7 +966,7 @@ jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *s // Step 5. Add the result to the execution engine now jl_finalize_module(m.release(), !toplevel); - if (li->jlcall_api != 2) { + if (world && li->jlcall_api != 2) { // if not inlineable, code won't be needed again if (JL_DELETE_NON_INLINEABLE && jl_options.debug_level <= 1 && li->def && li->inferred && jl_is_code_info(li->inferred) && @@ -1084,7 +1110,6 @@ static jl_method_instance_t *jl_get_unspecialized(jl_method_instance_t *method) if (def->unspecialized == NULL) { JL_LOCK(&def->writelock); if (def->unspecialized == NULL) { - // XXX: use computed env rather than empty svec def->unspecialized = jl_get_specialized(def, def->sig, jl_emptysvec); jl_gc_wb(def, def->unspecialized); } @@ -1095,7 +1120,7 @@ static jl_method_instance_t *jl_get_unspecialized(jl_method_instance_t *method) // this compiles li and emits fptr extern "C" -jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, void *_F) +jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, void *_F, size_t world) { Function *F = (Function*)_F; jl_generic_fptr_t fptr; @@ -1117,33 +1142,36 @@ jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, void *_F) return fptr; } jl_method_instance_t *unspec = NULL; - if (li->def && !li->def->isstaged && li->def->unspecialized) { - unspec = li->def->unspecialized; - } - if (!F || !jl_can_finalize_function(F)) { - // can't compile F in the JIT right now, - // so instead compile an unspecialized version - // and return its fptr instead - if (!unspec) - unspec = jl_get_unspecialized(li); // get-or-create the unspecialized version to cache the result - jl_code_info_t *src = unspec->def->isstaged ? jl_code_for_staged(unspec) : unspec->def->source; - fptr.fptr = unspec->fptr; - fptr.jlcall_api = unspec->jlcall_api; - if (fptr.fptr && fptr.jlcall_api) { - JL_UNLOCK(&codegen_lock); - return fptr; - } - jl_llvm_functions_t decls = unspec->functionObjectsDecls; - if (unspec == li) { - // temporarily clear the decls so that it will compile our unspec version of src - unspec->functionObjectsDecls.functionObject = NULL; - unspec->functionObjectsDecls.specFunctionObject = NULL; - } - F = (Function*)jl_compile_linfo(unspec, src, &jl_default_cgparams).functionObject; - if (unspec == li) { - unspec->functionObjectsDecls = decls; + if (li->def) { + if (!li->def->isstaged && li->def->unspecialized) { + unspec = li->def->unspecialized; + } + if (!F || !jl_can_finalize_function(F)) { + // can't compile F in the JIT right now, + // so instead compile an unspecialized version + // and return its fptr instead + if (!unspec) + unspec = jl_get_unspecialized(li); // get-or-create the unspecialized version to cache the result + jl_code_info_t *src = unspec->def->isstaged ? jl_code_for_staged(unspec) : unspec->def->source; + fptr.fptr = unspec->fptr; + fptr.jlcall_api = unspec->jlcall_api; + if (fptr.fptr && fptr.jlcall_api) { + JL_UNLOCK(&codegen_lock); + return fptr; + } + jl_llvm_functions_t decls = unspec->functionObjectsDecls; + if (unspec == li) { + // temporarily clear the decls so that it will compile our unspec version of src + unspec->functionObjectsDecls.functionObject = NULL; + unspec->functionObjectsDecls.specFunctionObject = NULL; + } + assert(src); + F = (Function*)jl_compile_linfo(&unspec, src, unspec->min_world, &jl_default_cgparams).functionObject; // this does not change unspec + if (unspec == li) { + unspec->functionObjectsDecls = decls; + } + assert(jl_can_finalize_function(F)); } - assert(jl_can_finalize_function(F)); } assert(F); fptr.fptr = (jl_fptr_t)getAddressForFunction(F); @@ -1251,7 +1279,7 @@ void jl_extern_c(jl_function_t *f, jl_value_t *rt, jl_value_t *argt, char *name) // this is paired with jl_dump_function_ir and jl_dump_function_asm in particular ways: // misuse will leak memory or cause read-after-free extern "C" JL_DLLEXPORT -void *jl_get_llvmf_defn(jl_method_instance_t *linfo, bool getwrapper, bool optimize, const jl_cgparams_t params) +void *jl_get_llvmf_defn(jl_method_instance_t *linfo, size_t world, bool getwrapper, bool optimize, const jl_cgparams_t params) { // `source` is `NULL` for generated functions. // The `isstaged` check can be removed if that is not the case anymore. @@ -1263,7 +1291,7 @@ void *jl_get_llvmf_defn(jl_method_instance_t *linfo, bool getwrapper, bool optim jl_code_info_t *src = (jl_code_info_t*)linfo->inferred; JL_GC_PUSH1(&src); if (!src || !jl_is_code_info(src)) { - src = jl_type_infer(linfo, 0); + src = jl_type_infer(&linfo, world, 0); if (!src) src = linfo->def->isstaged ? jl_code_for_staged(linfo) : linfo->def->source; } @@ -1279,7 +1307,7 @@ void *jl_get_llvmf_defn(jl_method_instance_t *linfo, bool getwrapper, bool optim jl_llvm_functions_t declarations; std::unique_ptr m; JL_TRY { - m = emit_function(linfo, src, &declarations, ¶ms); + m = emit_function(linfo, src, world, &declarations, ¶ms); } JL_CATCH { // something failed! @@ -1330,7 +1358,7 @@ void *jl_get_llvmf_defn(jl_method_instance_t *linfo, bool getwrapper, bool optim extern "C" JL_DLLEXPORT -void *jl_get_llvmf_decl(jl_method_instance_t *linfo, bool getwrapper, const jl_cgparams_t params) +void *jl_get_llvmf_decl(jl_method_instance_t *linfo, size_t world, bool getwrapper, const jl_cgparams_t params) { // `source` is `NULL` for generated functions. // The `isstaged` check can be removed if that is not the case anymore. @@ -1340,7 +1368,7 @@ void *jl_get_llvmf_decl(jl_method_instance_t *linfo, bool getwrapper, const jl_c } // compile this normally - jl_llvm_functions_t decls = jl_compile_for_dispatch(linfo); + jl_llvm_functions_t decls = jl_compile_for_dispatch(&linfo, world); if (decls.functionObject == NULL && linfo->jlcall_api == 2 && linfo->def) { // normally we don't generate native code for these functions, so need an exception here @@ -1349,11 +1377,11 @@ void *jl_get_llvmf_decl(jl_method_instance_t *linfo, bool getwrapper, const jl_c decls = linfo->functionObjectsDecls; if (decls.functionObject == NULL) { jl_code_info_t *src = NULL; - src = jl_type_infer(linfo, 0); + src = jl_type_infer(&linfo, world, 0); if (!src) { src = linfo->def->isstaged ? jl_code_for_staged(linfo) : linfo->def->source; } - decls = jl_compile_linfo(linfo, src, ¶ms); + decls = jl_compile_linfo(&linfo, src, world, ¶ms); linfo->functionObjectsDecls = decls; } JL_UNLOCK(&codegen_lock); @@ -1366,37 +1394,6 @@ void *jl_get_llvmf_decl(jl_method_instance_t *linfo, bool getwrapper, const jl_c } -extern "C" JL_DLLEXPORT -void *jl_get_llvmf(jl_tupletype_t *tt, bool getwrapper, bool getdeclarations) -{ // DEPRECATED - jl_method_instance_t *linfo = NULL, *temp = NULL; - JL_GC_PUSH3(&linfo, &temp, &tt); - if (tt != NULL) { - linfo = jl_get_specialization1(tt); - if (linfo == NULL) { - linfo = jl_method_lookup_by_type( - ((jl_datatype_t*)jl_tparam0(tt))->name->mt, tt, 0, 0, 1); - if (linfo == NULL || jl_has_call_ambiguities(tt, linfo->def)) { - JL_GC_POP(); - return NULL; - } - } - } - if (linfo == NULL) { - // no function found for argument tuple type - JL_GC_POP(); - return NULL; - } - void *f; - if (getdeclarations) - f = jl_get_llvmf_decl(linfo, getwrapper, jl_default_cgparams); - else - f = jl_get_llvmf_defn(linfo, getwrapper, true, jl_default_cgparams); - JL_GC_POP(); - return f; -} - - // print an llvm IR acquired from jl_get_llvmf // warning: this takes ownership of, and destroys, f->getParent() extern "C" JL_DLLEXPORT @@ -2968,7 +2965,7 @@ static jl_cgval_t emit_invoke(jl_expr_t *ex, jl_codectx_t *ctx) if (lival.constant) { jl_method_instance_t *li = (jl_method_instance_t*)lival.constant; assert(jl_is_method_instance(li)); - jl_llvm_functions_t decls = jl_compile_linfo(li, NULL, ctx->params); + jl_llvm_functions_t decls = jl_compile_linfo(&li, NULL, ctx->world, ctx->params); if (li->jlcall_api == 2) { assert(li->inferred); return mark_julia_const(li->inferred); @@ -3390,6 +3387,10 @@ static void emit_stmtpos(jl_value_t *expr, jl_codectx_t *ctx) ConstantInt::get(T_int32, jl_unbox_long(args[0]))); } else { + if (ctx->linfo->def == NULL) { + Value *world = builder.CreateCall(prepare_call(jlgetworld_func)); + builder.CreateStore(world, ctx->world_age_field); + } (void)emit_expr(expr, ctx); } } @@ -3662,15 +3663,16 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t const char *name = "cfunction"; // try to look up this function for direct invoking - jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt); + size_t world = jl_world_counter/*FIXME*/; + jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world); jl_value_t *astrt = (jl_value_t*)jl_any_type; // infer it first, if necessary if (lam) { name = jl_symbol_name(lam->def->name); jl_code_info_t *src = NULL; if (!lam->inferred) // TODO: this isn't ideal to be unconditionally calling type inference from here - src = jl_type_infer(lam, 0); - jl_compile_linfo(lam, src, &jl_default_cgparams); + src = jl_type_infer(&lam, world, 0); + jl_compile_linfo(&lam, src, world, &jl_default_cgparams); if (lam->jlcall_api != 2) { if (lam->functionObjectsDecls.functionObject == NULL || jl_jlcall_api(lam->functionObjectsDecls.functionObject) != 1) { @@ -3713,6 +3715,8 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t jl_codectx_t ctx = {}; ctx.f = cw; ctx.linfo = lam; + ctx.code = NULL; + ctx.world = jl_world_counter; ctx.sret = false; ctx.spvals_ptr = NULL; ctx.params = &jl_default_cgparams; @@ -4015,8 +4019,9 @@ static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_t cfunc_sig = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)cfunc_sig); // check the cache + size_t world = 1; if (jl_cfunction_list.unknown != jl_nothing) { - jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(jl_cfunction_list, (jl_tupletype_t*)cfunc_sig, NULL, 1, 0, /*offs*/0); + jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(jl_cfunction_list, (jl_tupletype_t*)cfunc_sig, NULL, 1, 0, /*offs*/0, world); if (sf) { Function *f = (Function*)jl_unbox_voidpointer(sf->func.value); if (f) { @@ -4026,7 +4031,7 @@ static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_t } } jl_typemap_entry_t *sf = jl_typemap_insert(&jl_cfunction_list, (jl_value_t*)jl_cfunction_list.unknown, (jl_tupletype_t*)cfunc_sig, - jl_emptysvec, NULL, jl_emptysvec, NULL, /*offs*/0, &cfunction_cache, NULL); + jl_emptysvec, NULL, jl_emptysvec, NULL, /*offs*/0, &cfunction_cache, 1, ~(size_t)0, NULL); // Backup the info for the nested compile JL_LOCK(&codegen_lock); @@ -4074,6 +4079,8 @@ static Function *gen_jlcall_wrapper(jl_method_instance_t *lam, Function *f, bool jl_codectx_t ctx = {}; ctx.f = w; ctx.linfo = lam; + ctx.code = NULL; + ctx.world = 0; ctx.sret = false; ctx.spvals_ptr = NULL; ctx.params = &jl_default_cgparams; @@ -4126,7 +4133,12 @@ static Function *gen_jlcall_wrapper(jl_method_instance_t *lam, Function *f, bool } // Compile to LLVM IR, using a specialized signature if applicable. -static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_info_t *src, jl_llvm_functions_t *declarations, const jl_cgparams_t *params) +static std::unique_ptr emit_function( + jl_method_instance_t *lam, + jl_code_info_t *src, + size_t world, + jl_llvm_functions_t *declarations, + const jl_cgparams_t *params) { jl_ptls_t ptls = jl_get_ptls_states(); assert(declarations && "Capturing declarations is always required"); @@ -4146,6 +4158,7 @@ static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_ ctx.linfo = lam; ctx.source = src; ctx.code = code; + ctx.world = world; ctx.name = jl_symbol_name(lam->def ? lam->def->name : anonymous_sym); ctx.funcName = ctx.name; ctx.vaSlot = -1; @@ -4153,6 +4166,7 @@ static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_ ctx.params = params; ctx.spvals_ptr = NULL; ctx.nargs = lam->def ? lam->def->nargs : 0; + bool toplevel = lam->def == NULL; // step 2. process var-info lists to see what vars need boxing int n_ssavalues = jl_is_long(src->ssavaluetypes) ? jl_unbox_long(src->ssavaluetypes) : jl_array_len(src->ssavaluetypes); @@ -4571,6 +4585,14 @@ static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_ // step 7. set up GC frame allocate_gc_frame(b0, &ctx); + Value *last_age = NULL; + if (toplevel) { + ctx.world_age_field = builder.CreateGEP( + builder.CreateBitCast(ctx.ptlsStates, T_psize), + ConstantInt::get(T_size, offsetof(jl_tls_states_t, world_age) / sizeof(size_t))); + last_age = tbaa_decorate(tbaa_gcframe, builder.CreateLoad(ctx.world_age_field)); + } + // step 8. allocate local variables slots // must be in the first basic block for the llvm mem2reg pass to work @@ -5093,6 +5115,8 @@ static std::unique_ptr emit_function(jl_method_instance_t *lam, jl_code_ } if (do_malloc_log(props.in_user_code) && props.line != -1) mallocVisitLine(props.file, props.line); + if (toplevel) + builder.CreateStore(last_age, ctx.world_age_field); if (type_is_ghost(retty) || ctx.sret) builder.CreateRetVoid(); else @@ -5680,6 +5704,7 @@ static void init_julia_llvm_env(Module *m) builtin_func_map[jl_f_isa] = jlcall_func_to_llvm("jl_f_isa", &jl_f_isa, m); builtin_func_map[jl_f_typeassert] = jlcall_func_to_llvm("jl_f_typeassert", &jl_f_typeassert, m); builtin_func_map[jl_f__apply] = jlcall_func_to_llvm("jl_f__apply", &jl_f__apply, m); + builtin_func_map[jl_f__apply_pure] = jlcall_func_to_llvm("jl_f__apply_pure", &jl_f__apply_pure, m); builtin_func_map[jl_f_throw] = jlcall_func_to_llvm("jl_f_throw", &jl_f_throw, m); builtin_func_map[jl_f_tuple] = jlcall_func_to_llvm("jl_f_tuple", &jl_f_tuple, m); builtin_func_map[jl_f_svec] = jlcall_func_to_llvm("jl_f_svec", &jl_f_svec, m); @@ -5958,6 +5983,13 @@ static void init_julia_llvm_env(Module *m) except_enter_func->addFnAttr(Attribute::ReturnsTwice); add_named_global(except_enter_func, (void*)NULL, /*dllimport*/false); + jlgetworld_func = + Function::Create(FunctionType::get(T_size, ArrayRef(), false), + Function::ExternalLinkage, + "jl_get_world_counter", m); + jlgetworld_func->addFnAttr(Attribute::ReadOnly); + add_named_global(jlgetworld_func, jl_get_world_counter); + // set up optimization passes #if JL_LLVM_VERSION >= 30700 // No DataLayout pass needed anymore. diff --git a/src/dump.c b/src/dump.c index 1acde727d2760..32f1c5c91c766 100644 --- a/src/dump.c +++ b/src/dump.c @@ -73,11 +73,11 @@ static htable_t fptr_to_id; static const jl_fptr_t id_to_fptrs[] = { NULL, NULL, jl_f_throw, jl_f_is, jl_f_typeof, jl_f_issubtype, jl_f_isa, - jl_f_typeassert, jl_f__apply, jl_f_isdefined, jl_f_tuple, jl_f_svec, + jl_f_typeassert, jl_f__apply, jl_f__apply_pure, jl_f_isdefined, + jl_f_tuple, jl_f_svec, jl_f_intrinsic_call, jl_f_getfield, jl_f_setfield, jl_f_fieldtype, jl_f_nfields, jl_f_arrayref, jl_f_arrayset, jl_f_arraysize, jl_f_apply_type, jl_f_applicable, jl_f_invoke, jl_unprotect_stack, jl_f_sizeof, jl_f__expr, - jl_f_intrinsic_call, NULL }; static const intptr_t LongSymbol_tag = 23; @@ -748,12 +748,14 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v) arraylist_push(&reinit_list, (void*)pos); arraylist_push(&reinit_list, (void*)1); } - if (s->mode == MODE_MODULE && jl_is_module(v)) { - jl_module_t *m = (jl_module_t*)v; - if (module_in_worklist(m) && !module_in_worklist(m->parent)) { - // will need to reinsert this into parent bindings, later (in case of any errors during reinsert) - arraylist_push(&reinit_list, (void*)pos); - arraylist_push(&reinit_list, (void*)2); + if (s->mode == MODE_MODULE) { + if (jl_is_module(v)) { + jl_module_t *m = (jl_module_t*)v; + if (module_in_worklist(m) && !module_in_worklist(m->parent)) { + // will need to reinsert this into parent bindings, later (in case of any errors during reinsert) + arraylist_push(&reinit_list, (void*)pos); + arraylist_push(&reinit_list, (void*)2); + } } } if (s->mode == MODE_MODULE) { @@ -898,6 +900,13 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v) write_int8(s->s, m->isstaged); jl_serialize_value(s, (jl_value_t*)m->file); write_int32(s->s, m->line); + if (s->mode != MODE_MODULE) { + write_int32(s->s, m->min_world); + write_int32(s->s, m->max_world); + } + else { + assert(m->max_world == ~(size_t)0 && "method replacement cannot be handled by incremental serializer"); + } jl_serialize_value(s, (jl_value_t*)m->tvars); if (external_mt) jl_serialize_value(s, jl_nothing); @@ -917,10 +926,20 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v) else if (jl_is_method_instance(v)) { writetag(s->s, jl_method_instance_type); jl_method_instance_t *li = (jl_method_instance_t*)v; - int external = 0; + int internal = 0; if (s->mode == MODE_MODULE) { - external = li->def && !module_in_worklist(li->def->module); - if (external) { + if (li->max_world == 0 && li->min_world == 0) { + internal = 1; // not world-tracked + } + else if (!li->def || module_in_worklist(li->def->module)) { + if (li->max_world == ~(size_t)0) { + internal = 2; // update world on deserialization + } + else { + internal = 3; // garbage object :( + } + } + if (!internal) { // also flag this in the backref table as special uintptr_t *bp = (uintptr_t*)ptrhash_bp(&backref_table, v); assert(*bp != (uintptr_t)HT_NOTFOUND); @@ -928,19 +947,24 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v) } } jl_serialize_value(s, (jl_value_t*)li->specTypes); - if (s->mode == MODE_MODULE && external) + if (s->mode == MODE_MODULE && !internal) jl_serialize_value(s, (jl_value_t*)li->def->sig); else jl_serialize_value(s, (jl_value_t*)li->def); if (s->mode == MODE_MODULE) { - write_uint8(s->s, external); - if (external) + write_uint8(s->s, internal); + if (!internal) return; } jl_serialize_value(s, li->inferred); jl_serialize_value(s, li->inferred_const); jl_serialize_value(s, li->rettype); jl_serialize_value(s, (jl_value_t*)li->sparam_vals); + jl_serialize_value(s, (jl_value_t*)li->backedges); + if (s->mode != MODE_MODULE) { + write_int32(s->s, li->min_world); + write_int32(s->s, li->max_world); + } if (li->def) { uint16_t id = jl_fptr_id((void*)(uintptr_t)li->fptr); if (li->jlcall_api == 2) { @@ -1066,38 +1090,66 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v) } } -struct jl_serialize_methcache_from_mod_env { - jl_serializer_state *s; - jl_sym_t *name; - jl_module_t *mod; -}; +static void jl_serialize_missing_backedges_to_mod(jl_serializer_state *s, jl_methtable_t *mt) +{ + jl_array_t *backedges = mt->backedges; + if (backedges) { + size_t i, l = jl_array_len(backedges); + for (i = 1; i < l; i += 2) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); + if (caller->max_world == ~(size_t)0 && module_in_worklist(caller->def->module)) { + jl_serialize_value(s, caller); + jl_serialize_value(s, jl_array_ptr_ref(backedges, i - 1)); + } + } + } +} + +static int jl_serialize_backedges_to_mod(jl_typemap_entry_t *ml, void *closure) +{ + jl_serializer_state *s = (jl_serializer_state*)closure; + jl_method_instance_t *callee = ml->func.linfo; + jl_array_t *backedges = callee->backedges; + if (backedges) { + assert(callee->max_world == ~(size_t)0); + size_t i, l = jl_array_len(backedges); + for (i = 0; i < l; i++) { + jl_method_instance_t *caller = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); + if (caller->max_world == ~(size_t)0 && module_in_worklist(caller->def->module)) { + jl_serialize_value(s, caller); + jl_serialize_value(s, callee); + } + } + } + return 1; +} static int jl_serialize_methcache_from_mod(jl_typemap_entry_t *ml, void *closure) { - struct jl_serialize_methcache_from_mod_env *env = (struct jl_serialize_methcache_from_mod_env*)closure; - if (module_in_worklist(ml->func.method->module)) { - jl_serialize_value(env->s, ml->func.method); - jl_serialize_value(env->s, ml->simplesig); + jl_serializer_state *s = (jl_serializer_state*)closure; + jl_method_t *m = ml->func.method; + if (module_in_worklist(m->module)) { + jl_serialize_value(s, m); + jl_serialize_value(s, ml->simplesig); + } + else { + jl_typemap_visitor(m->specializations, jl_serialize_backedges_to_mod, closure); } return 1; } static void jl_serialize_methtable_from_mod(jl_serializer_state *s, jl_typename_t *tn) { - struct jl_serialize_methcache_from_mod_env env; - env.s = s; - env.mod = tn->module; - env.name = tn->name; - assert(tn->module); - jl_typemap_visitor(tn->mt->defs, jl_serialize_methcache_from_mod, &env); + jl_typemap_visitor(tn->mt->defs, jl_serialize_methcache_from_mod, (void*)s); } static void jl_serialize_lambdas_from_mod(jl_serializer_state *s, jl_module_t *m) { - if (module_in_worklist(m)) return; + if (module_in_worklist(m)) + return; size_t i; void **table = m->bindings.table; - for(i=1; i < m->bindings.size; i+=2) { + for (i = 1; i < m->bindings.size; i += 2) { if (table[i] != HT_NOTFOUND) { jl_binding_t *b = (jl_binding_t*)table[i]; if (b->owner == m && b->value && b->constp) { @@ -1105,8 +1157,11 @@ static void jl_serialize_lambdas_from_mod(jl_serializer_state *s, jl_module_t *m jl_typename_t *tn = ((jl_datatype_t*)b->value)->name; if (tn->module == m && tn->name == b->name) { jl_methtable_t *mt = tn->mt; - if (mt != NULL && (jl_value_t*)mt != jl_nothing && (mt != jl_type_type_mt || tn == jl_type_type->name)) { + if (mt != NULL && + (jl_value_t*)mt != jl_nothing && + (mt != jl_type_type_mt || tn == jl_type_type->name)) { jl_serialize_methtable_from_mod(s, tn); + jl_serialize_missing_backedges_to_mod(s, mt); } } } @@ -1128,7 +1183,7 @@ static void write_mod_list(ios_t *s) jl_module_t *m = jl_main_module; size_t i; void **table = m->bindings.table; - for(i=1; i < m->bindings.size; i+=2) { + for (i = 1; i < m->bindings.size; i += 2) { if (table[i] != HT_NOTFOUND) { jl_binding_t *b = (jl_binding_t*)table[i]; if (b->owner == m && @@ -1197,8 +1252,11 @@ static void write_dependency_list(ios_t *s) static jl_value_t *unique_func = NULL; if (!unique_func) unique_func = jl_get_global(jl_base_module, jl_symbol("unique")); - jl_value_t *uniqargs[2] = {unique_func,(jl_value_t*)deps}; + jl_value_t *uniqargs[2] = {unique_func, (jl_value_t*)deps}; + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_world_counter; jl_array_t *udeps = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL; + jl_get_ptls_states()->world_age = last_age; JL_GC_PUSH1(&udeps); if (udeps) { @@ -1539,6 +1597,14 @@ static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_ m->isstaged = read_int8(s->s); m->file = (jl_sym_t*)jl_deserialize_value(s, NULL); m->line = read_int32(s->s); + if (s->mode != MODE_MODULE) { + m->min_world = read_int32(s->s); + m->max_world = read_int32(s->s); + } + else { + m->min_world = jl_world_counter; + m->max_world = ~(size_t)0; + } m->tvars = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->tvars); jl_gc_wb(m, m->tvars); m->ambig = jl_deserialize_value(s, (jl_value_t**)&m->ambig); @@ -1585,9 +1651,10 @@ static jl_value_t *jl_deserialize_value_method_instance(jl_serializer_state *s, if (li->def) jl_gc_wb(li, li->def); + int internal = 0; if (s->mode == MODE_MODULE) { - int external = read_uint8(s->s); - if (external) { + internal = read_uint8(s->s); + if (!internal) { assert(loc != NULL && loc != HT_NOTFOUND); arraylist_push(&flagref_list, loc); arraylist_push(&flagref_list, (void*)pos); @@ -1604,7 +1671,30 @@ static jl_value_t *jl_deserialize_value_method_instance(jl_serializer_state *s, jl_gc_wb(li, li->rettype); li->sparam_vals = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&li->sparam_vals); jl_gc_wb(li, li->sparam_vals); + li->backedges = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&li->backedges); + if (li->backedges) + jl_gc_wb(li, li->backedges); li->unspecialized_ducttape = NULL; + if (s->mode != MODE_MODULE) { + li->min_world = read_int32(s->s); + li->max_world = read_int32(s->s); + } + else if (internal == 1) { + li->min_world = 0; + li->max_world = 0; + } + else if (internal == 2) { + li->min_world = jl_world_counter; + li->max_world = ~(size_t)0; + } + else if (internal == 3) { + li->min_world = 1; + li->max_world = 0; + } + else { + assert(0 && "corrupt deserialization state"); + abort(); + } li->functionObjectsDecls.functionObject = NULL; li->functionObjectsDecls.specFunctionObject = NULL; li->inInference = 0; @@ -1774,6 +1864,16 @@ static jl_value_t *jl_deserialize_value_any(jl_serializer_state *s, jl_value_t * tn->cache = jl_emptysvec; // the cache is refilled later (tag 5) tn->linearcache = jl_emptysvec; // the cache is refilled later (tag 5) } + if (dt == jl_typemap_entry_type) { + if (((jl_typemap_entry_t*)v)->max_world == ~(size_t)0) { + // update world validity to reflect current state of the counter + ((jl_typemap_entry_t*)v)->min_world = jl_world_counter; + } + else { + // garbage entry - delete it :( + ((jl_typemap_entry_t*)v)->min_world = ((jl_typemap_entry_t*)v)->max_world - 1; + } + } } } return v; @@ -1867,9 +1967,15 @@ static jl_value_t *jl_deserialize_value_(jl_serializer_state *s, jl_value_t *vta typedef struct _linkedlist_t { struct _linkedlist_t *next; - struct { - jl_method_t *meth; - jl_tupletype_t *simpletype; + union { + struct { + jl_method_t *meth; + jl_tupletype_t *simpletype; + }; + struct { + jl_method_instance_t *caller; + jl_value_t *callee; + }; } def[100]; size_t count; } linkedlist_t; @@ -1902,15 +2008,41 @@ static void jl_insert_methods(linkedlist_t *list) while (list) { size_t i; for (i = 0; i < list->count; i++) { - jl_method_t *meth = list->def[i].meth; - jl_datatype_t *gf = jl_first_argument_datatype((jl_value_t*)meth->sig); - assert(jl_is_datatype(gf) && gf->name->mt); - jl_method_table_insert(gf->name->mt, meth, list->def[i].simpletype); + if (jl_is_method(list->def[i].meth)) { + jl_method_t *meth = list->def[i].meth; + jl_datatype_t *gf = jl_first_argument_datatype((jl_value_t*)meth->sig); + assert(jl_is_datatype(gf) && gf->name->mt); + jl_method_table_insert(gf->name->mt, meth, list->def[i].simpletype); + } } list = list->next; } } +static void jl_insert_backedges(linkedlist_t *list) +{ + while (list) { + size_t i; + for (i = 0; i < list->count; i++) { + if (!jl_is_method(list->def[i].meth)) { + jl_method_instance_t *caller = list->def[i].caller; + assert(jl_is_method_instance(caller)); + jl_value_t *callee = list->def[i].callee; + if (jl_is_method_instance(callee)) { + jl_method_instance_add_backedge((jl_method_instance_t*)callee, caller); + } + else { + jl_datatype_t *gf = jl_first_argument_datatype(callee); + assert(jl_is_datatype(gf) && gf->name->mt); + jl_method_table_add_backedge(gf->name->mt, callee, (jl_value_t*)caller); + } + } + } + list = list->next; + } +} + + static void free_linkedlist(linkedlist_t *list) { while (list) { @@ -2151,6 +2283,7 @@ static void jl_save_system_image_to_stream(ios_t *f) jl_serialize_value(&s, jl_main_module); jl_serialize_value(&s, jl_top_module); jl_serialize_value(&s, jl_typeinf_func); + write_uint64(f, jl_typeinf_world); // deserialize method tables of builtin types jl_serialize_value(&s, jl_type_type->name->mt); @@ -2179,6 +2312,7 @@ static void jl_save_system_image_to_stream(ios_t *f) write_int32(f, jl_get_t_uid_ctr()); write_int32(f, jl_get_gs_ctr()); + write_int32(f, jl_world_counter); jl_finalize_serializer(&s); // done with f and s htable_reset(&backref_table, 0); @@ -2207,7 +2341,6 @@ JL_DLLEXPORT ios_t *jl_create_system_image(void) return f; } -extern jl_function_t *jl_typeinf_func; extern int jl_boot_file_loaded; extern void jl_get_builtins(void); extern void jl_get_builtin_hooks(void); @@ -2254,8 +2387,9 @@ static void jl_restore_system_image_from_stream(ios_t *f) jl_main_module = (jl_module_t*)jl_deserialize_value(&s, NULL); jl_top_module = (jl_module_t*)jl_deserialize_value(&s, NULL); jl_internal_main_module = jl_main_module; - jl_typeinf_func = (jl_function_t*)jl_deserialize_value(&s, NULL); + jl_typeinf_world = read_uint64(f); + jl_type_type_mt = (jl_methtable_t*)jl_deserialize_value(&s, NULL); jl_type_type->name->mt = jl_type_type_mt; jl_typector_type->name->mt = jl_type_type_mt; @@ -2289,6 +2423,7 @@ static void jl_restore_system_image_from_stream(ios_t *f) int uid_ctr = read_int32(f); int gs_ctr = read_int32(f); + jl_world_counter = read_int32(f); jl_module_init_order = jl_finalize_deserializer(&s, NULL); // done with s and f jl_set_t_uid_ctr(uid_ctr); @@ -2631,7 +2766,7 @@ jl_method_t *jl_recache_method(jl_method_t *m, size_t start) jl_datatype_t *ftype = jl_first_argument_datatype((jl_value_t*)sig); jl_methtable_t *mt = ftype->name->mt; jl_set_typeof(m, (void*)(intptr_t)0x30); // invalidate the old value to help catch errors - jl_method_t *_new = (jl_method_t*)jl_methtable_lookup(mt, sig); + jl_method_t *_new = (jl_method_t*)jl_methtable_lookup(mt, sig, /*TODO*/jl_world_counter); assert(_new && jl_is_method(_new)); jl_update_backref_list((jl_value_t*)m, (jl_value_t*)_new, start); return _new; @@ -2643,7 +2778,7 @@ jl_method_instance_t *jl_recache_method_instance(jl_method_instance_t *li, size_ jl_datatype_t *sig = (jl_datatype_t*)li->def; jl_datatype_t *ftype = jl_first_argument_datatype((jl_value_t*)sig); jl_methtable_t *mt = ftype->name->mt; - jl_method_t *m = (jl_method_t*)jl_methtable_lookup(mt, sig); + jl_method_t *m = (jl_method_t*)jl_methtable_lookup(mt, sig, /*TODO*/jl_world_counter); assert(m && jl_is_method(m)); jl_datatype_t *argtypes = li->specTypes; @@ -2653,7 +2788,7 @@ jl_method_instance_t *jl_recache_method_instance(jl_method_instance_t *li, size_ //assert(ti != jl_bottom_type); (void)ti; if (ti == jl_bottom_type) env = jl_emptysvec; // the intersection may fail now if the type system had made an incorrect subtype env in the past - jl_method_instance_t *_new = jl_specializations_get_linfo(m, argtypes, env); + jl_method_instance_t *_new = jl_specializations_get_linfo(m, argtypes, env, /*TODO*/jl_world_counter); jl_update_backref_list((jl_value_t*)li, (jl_value_t*)_new, start); return _new; } @@ -2721,6 +2856,7 @@ static jl_value_t *_jl_restore_incremental(ios_t *f) arraylist_new(&flagref_list, 0); int en = jl_gc_enable(0); + ++jl_world_counter; // reserve a world age for the deserialization jl_serializer_state s = { f, MODE_MODULE, NULL, NULL, @@ -2742,9 +2878,10 @@ static jl_value_t *_jl_restore_incremental(ios_t *f) // at this point, the AST is fully reconstructed, but still completely disconnected // now all of the interconnects will be created jl_recache_types(); // make all of the types identities correct - jl_recache_other(); // make all of the other objects identities correct - init_order = jl_finalize_deserializer(&s, tracee_list); // done with f and s - jl_insert_methods(&external_methods); // hook up methods of external generic functions + init_order = jl_finalize_deserializer(&s, tracee_list); // done with f and s (needs to be after recache types) + jl_insert_methods(&external_methods); // hook up methods of external generic functions (needs to be after recache types) + jl_recache_other(); // make all of the other objects identities correct (needs to be after insert methods) + jl_insert_backedges(&external_methods); // restore external backedges (needs to be after recache other) free_linkedlist(external_methods.next); serializer_worklist = NULL; diff --git a/src/gc.c b/src/gc.c index cd35e9d6419a0..d32cd98dbd8ad 100644 --- a/src/gc.c +++ b/src/gc.c @@ -105,7 +105,10 @@ static void run_finalizer(jl_ptls_t ptls, jl_value_t *o, jl_value_t *ff) assert(!jl_typeis(ff, jl_voidpointer_type)); jl_value_t *args[2] = {ff,o}; JL_TRY { + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_world_counter; jl_apply(args, 2); + jl_get_ptls_states()->world_age = last_age; } JL_CATCH { jl_printf(JL_STDERR, "error in running finalizer: "); diff --git a/src/gf.c b/src/gf.c index adca9a84774cc..436a73994a9fe 100644 --- a/src/gf.c +++ b/src/gf.c @@ -24,9 +24,32 @@ extern "C" { #endif +size_t jl_world_counter = 1; +JL_DLLEXPORT size_t jl_get_world_counter(void) +{ + return jl_world_counter; +} + +JL_DLLEXPORT size_t jl_get_tls_world_age(void) +{ + return jl_get_ptls_states()->world_age; +} + JL_DLLEXPORT jl_value_t *jl_invoke(jl_method_instance_t *meth, jl_value_t **args, uint32_t nargs) { - return jl_call_method_internal(meth, args, nargs); + if (meth->jlcall_api) { + return jl_call_method_internal(meth, args, nargs); + } + else { + // if this hasn't been inferred (compiled) yet, + // inferring it might not be able to handle the world range + // so we just do a generic apply here + // because that might actually be faster + // since it can go through the unrolled caches for this world + // and if inference is successful, this meth would get updated anyways, + // and we'll get the fast path here next time + return jl_apply(args, nargs); + } } /// ----- Handling for Julia callbacks ----- /// @@ -117,34 +140,55 @@ static int8_t jl_cachearg_offset(jl_methtable_t *mt) /// ----- Insertion logic for special entries ----- /// // get or create the MethodInstance for a specialization -JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m, jl_tupletype_t *type, jl_svec_t *sparams) +JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m, jl_tupletype_t *type, jl_svec_t *sparams, size_t world) { + assert(world >= m->min_world && world <= m->max_world && "typemap lookup is corrupted"); JL_LOCK(&m->writelock); - jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(m->specializations, type, NULL, 2, /*subtype*/0, /*offs*/0); + jl_typemap_entry_t *sf = jl_typemap_assoc_by_type( + m->specializations, type, NULL, 2, /*subtype*/0, /*offs*/0, world); if (sf && jl_is_method_instance(sf->func.value)) { + jl_method_instance_t *linfo = (jl_method_instance_t*)sf->func.value; + assert(linfo->min_world <= sf->min_world && linfo->max_world >= sf->max_world); JL_UNLOCK(&m->writelock); - return (jl_method_instance_t*)sf->func.value; + return linfo; } jl_method_instance_t *li = jl_get_specialized(m, type, sparams); JL_GC_PUSH1(&li); // TODO: fuse lookup and insert steps - jl_typemap_insert(&m->specializations, (jl_value_t*)m, type, jl_emptysvec, NULL, jl_emptysvec, (jl_value_t*)li, 0, &tfunc_cache, NULL); + // pick an initial world that is likely to be valid both before and after inference + if (world > jl_world_counter) { + li->min_world = jl_world_counter; + } + else { + li->min_world = world; + } + if (world == jl_world_counter) { + assert(m->max_world == ~(size_t)0 && "method validity shouldn't be scheduled to terminate at a fixed future age"); + li->max_world = m->max_world; + } + else { + li->max_world = world; + } + jl_typemap_insert(&m->specializations, (jl_value_t*)m, type, jl_emptysvec, NULL, jl_emptysvec, (jl_value_t*)li, 0, &tfunc_cache, + li->min_world, li->max_world, NULL); JL_UNLOCK(&m->writelock); JL_GC_POP(); return li; } -JL_DLLEXPORT jl_value_t *jl_specializations_lookup(jl_method_t *m, jl_tupletype_t *type) +JL_DLLEXPORT jl_value_t *jl_specializations_lookup(jl_method_t *m, jl_tupletype_t *type, size_t world) { - jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(m->specializations, type, NULL, 2, /*subtype*/0, /*offs*/0); + jl_typemap_entry_t *sf = jl_typemap_assoc_by_type( + m->specializations, type, NULL, 2, /*subtype*/0, /*offs*/0, world); if (!sf) return jl_nothing; return sf->func.value; } -JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_tupletype_t *type) +JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_tupletype_t *type, size_t world) { - jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(mt->defs, type, NULL, 1, /*subtype*/0, /*offs*/0); + jl_typemap_entry_t *sf = jl_typemap_assoc_by_type( + mt->defs, type, NULL, 1, /*subtype*/0, /*offs*/0, world); if (!sf) return jl_nothing; return sf->func.value; @@ -166,6 +210,8 @@ void jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_t fptr) li->fptr = fptr; li->jlcall_api = 1; li->specTypes = jl_anytuple_type; + li->min_world = 1; + li->max_world = ~(size_t)0; li->def = jl_new_method_uninit(); li->def->name = sname; @@ -177,53 +223,187 @@ void jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_t fptr) li->def->sparam_syms = jl_emptysvec; jl_methtable_t *mt = dt->name->mt; - jl_typemap_insert(&mt->cache, (jl_value_t*)mt, jl_anytuple_type, jl_emptysvec, NULL, jl_emptysvec, (jl_value_t*)li, 0, &lambda_cache, NULL); + jl_typemap_insert(&mt->cache, (jl_value_t*)mt, jl_anytuple_type, jl_emptysvec, NULL, jl_emptysvec, (jl_value_t*)li, 0, &lambda_cache, 1, ~(size_t)0, NULL); } // run type inference on lambda "li" for given argument types. // returns the inferred source, and may cache the result in li +// if successful, also updates the li argument to describe the validity of this src // if inference doesn't occur (or can't finish), returns NULL instead -jl_code_info_t *jl_type_infer(jl_method_instance_t *li, int force) +jl_code_info_t *jl_type_infer(jl_method_instance_t **pli, size_t world, int force) { JL_TIMING(INFERENCE); if (jl_typeinf_func == NULL) return NULL; jl_code_info_t *src = NULL; #ifdef ENABLE_INFERENCE + jl_method_instance_t *li = *pli; jl_module_t *mod = NULL; if (li->def != NULL) mod = li->def->module; static int inInference = 0; int lastIn = inInference; + size_t last_age = jl_get_ptls_states()->world_age; inInference = 1; if (force || - (mod != jl_gf_mtable(jl_typeinf_func)->module && + (last_age != jl_typeinf_world && + mod != jl_gf_mtable(jl_typeinf_func)->module && (mod != jl_core_module || !lastIn))) { // avoid any potential recursion in calling jl_typeinf_func on itself - assert(li->inInference == 0); - jl_value_t *fargs[2]; + assert(li->inInference == 0 && "unexpectedly asked to infer a method that is already being inferred"); + jl_value_t **fargs; + JL_GC_PUSHARGS(fargs, 3); fargs[0] = (jl_value_t*)jl_typeinf_func; fargs[1] = (jl_value_t*)li; + fargs[2] = jl_box_ulong(world); #ifdef TRACE_INFERENCE jl_printf(JL_STDERR,"inference on "); jl_static_show_func_sig(JL_STDERR, (jl_value_t*)li->specTypes); jl_printf(JL_STDERR, "\n"); #endif - src = (jl_code_info_t *)jl_apply(fargs, 2); - if (src == (void*)jl_nothing) - src = NULL; - assert(li->def || li->inInference == 0); // if this is toplevel expr, make sure inference finished + jl_get_ptls_states()->world_age = jl_typeinf_world; + jl_svec_t *linfo_src_rettype = (jl_svec_t*)jl_apply(fargs, 3); + jl_get_ptls_states()->world_age = last_age; + assert((li->def || li->inInference == 0) && "inference failed on a toplevel expr"); + if (jl_is_svec(linfo_src_rettype) && jl_svec_len(linfo_src_rettype) == 3 && + jl_is_method_instance(jl_svecref(linfo_src_rettype, 0)) && + jl_is_code_info(jl_svecref(linfo_src_rettype, 1))) { + *pli = (jl_method_instance_t*)jl_svecref(linfo_src_rettype, 0); + src = (jl_code_info_t*)jl_svecref(linfo_src_rettype, 1); + } + JL_GC_POP(); } inInference = lastIn; #endif return src; } -JL_DLLEXPORT void jl_set_lambda_rettype(jl_method_instance_t *li, jl_value_t *rettype, int32_t const_flags, jl_value_t *inferred_const, jl_value_t *inferred) + +static int jl_is_rettype_inferred(jl_method_instance_t *li) +{ + if (!li->inferred) + return 0; + if (jl_is_code_info(li->inferred) && !((jl_code_info_t*)li->inferred)->inferred) + return 0; + return 1; +} + + +struct set_world { + jl_method_instance_t *replaced; + size_t world; +}; +static int set_max_world2(jl_typemap_entry_t *entry, void *closure0) +{ + struct set_world *closure = (struct set_world*)closure0; + // entry->max_world should be <= closure->replaced->max_world and >= closure->world + if (entry->func.linfo == closure->replaced) { + entry->max_world = closure->world; + } + return 1; +} +static int set_min_world2(jl_typemap_entry_t *entry, void *closure0) { + struct set_world *closure = (struct set_world*)closure0; + // entry->min_world should be >= closure->replaced->min_world and >= closure->world + if (entry->func.linfo == closure->replaced) { + entry->min_world = closure->world; + } + return 1; +} +static void update_world_bound(jl_method_instance_t *replaced, jl_typemap_visitor_fptr fptr, size_t world) +{ + struct set_world update; + update.replaced = replaced; + update.world = world; + + jl_method_t *m = replaced->def; + // update the world-valid in the specializations caches + jl_typemap_visitor(m->specializations, fptr, (void*)&update); + // update the world-valid in the invoke cache + if (m->invokes.unknown != NULL) + jl_typemap_visitor(m->invokes, fptr, (void*)&update); + // update the world-valid in the gf cache + jl_datatype_t *gf = jl_first_argument_datatype((jl_value_t*)m->sig); + assert(jl_is_datatype(gf) && gf->name->mt && "method signature invalid?"); + jl_typemap_visitor(gf->name->mt->cache, fptr, (void*)&update); +} + + +JL_DLLEXPORT jl_method_instance_t* jl_set_method_inferred( + jl_method_instance_t *li, jl_value_t *rettype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world) +{ + JL_GC_PUSH1(&li); + assert(min_world <= max_world && "attempting to set invalid world constraints"); + assert(li->inInference && "shouldn't be caching an inference result for a MethodInstance that wasn't being inferred"); + if (min_world != li->min_world || max_world != li->max_world) { + if (li->def == NULL) { + // thunks don't have multiple references, so just update in-place + li->min_world = min_world; + li->max_world = max_world; + } + else { + JL_LOCK(&li->def->writelock); + assert(min_world >= li->def->min_world && max_world <= li->def->max_world); + int isinferred = jl_is_rettype_inferred(li); + if (!isinferred && li->min_world >= min_world && li->max_world <= max_world) { + // expand the current (uninferred) entry to cover the full inferred range + // only update the specializations though, since the method table may have other + // reasons for needing a narrower applicability range + struct set_world update; + update.replaced = li; + if (li->min_world != min_world) { + li->min_world = min_world; + update.world = min_world; + jl_typemap_visitor(li->def->specializations, set_min_world2, (void*)&update); + } + if (li->max_world != max_world) { + li->max_world = max_world; + update.world = max_world; + jl_typemap_visitor(li->def->specializations, set_max_world2, (void*)&update); + } + } + else { + // clip applicability of old method instance (uninferred or inferred) + // to make it easier to find the inferred method + // (even though the real applicability was unchanged) + // there are 6(!) regions here to consider + boundary conditions for each + if (li->max_world >= min_world && li->min_world <= max_world) { + // there is a non-zero overlap between [li->min, li->max] and [min, max] + // there are now 4 regions left to consider + // TODO: also take into account li->def->world range when computing preferred division + if (li->max_world > max_world) { + // prefer making it applicable to future ages, + // as those are more likely to be useful + update_world_bound(li, set_min_world2, max_world + 1); + } + else if (li->min_world < min_world) { + assert(min_world > 1 && "logic violation: min(li->min_world) == 1 (by construction), so min(min_world) == 2"); + update_world_bound(li, set_max_world2, min_world - 1); + } + else { + // old inferred li is fully covered by new inference result, so just delete it + assert(isinferred); + update_world_bound(li, set_max_world2, li->min_world - 1); + } + } + + // build a new entry to describe the new (inferred) applicability + li = jl_get_specialized(li->def, li->specTypes, li->sparam_vals); + li->min_world = min_world; + li->max_world = max_world; + jl_typemap_insert(&li->def->specializations, (jl_value_t*)li->def, + li->specTypes, jl_emptysvec, NULL, jl_emptysvec, + (jl_value_t*)li, 0, &tfunc_cache, + li->min_world, li->max_world, NULL); + } + JL_UNLOCK(&li->def->writelock); + } + } + // changing rettype changes the llvm signature, // so clear all of the llvm state at the same time - assert(li->inInference); - assert(!li->inferred || li->functionObjectsDecls.functionObject == NULL); // protect against some double-infer dataflow mistakes li->functionObjectsDecls.functionObject = NULL; li->functionObjectsDecls.specFunctionObject = NULL; li->rettype = rettype; @@ -238,20 +418,14 @@ JL_DLLEXPORT void jl_set_lambda_rettype(jl_method_instance_t *li, jl_value_t *re li->inferred_const = inferred_const; jl_gc_wb(li, inferred_const); } + JL_GC_POP(); + return li; } -static int jl_is_uninferred(jl_method_instance_t *li) -{ - if (!li->inferred) - return 1; - if (jl_is_code_info(li->inferred) && !((jl_code_info_t*)li->inferred)->inferred) - return 1; - return 0; -} static int get_spec_unspec_list(jl_typemap_entry_t *l, void *closure) { - if (jl_is_method_instance(l->func.value) && jl_is_uninferred(l->func.linfo)) + if (jl_is_method_instance(l->func.value) && !jl_is_rettype_inferred(l->func.linfo)) jl_array_ptr_1d_push((jl_array_t*)closure, l->func.value); return 1; } @@ -262,6 +436,7 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) return 1; } + static void jl_reset_mt_caches(jl_module_t *m, jl_array_t *unspec) { // removes all method caches @@ -294,20 +469,24 @@ static void jl_reset_mt_caches(jl_module_t *m, jl_array_t *unspec) } } -jl_function_t *jl_typeinf_func=NULL; +jl_function_t *jl_typeinf_func = NULL; +size_t jl_typeinf_world = 0; JL_DLLEXPORT void jl_set_typeinf_func(jl_value_t *f) { jl_typeinf_func = (jl_function_t*)f; + jl_typeinf_world = jl_get_tls_world_age(); + ++jl_world_counter; // make type-inference the only thing in this world // give type inference a chance to see all of these + // TODO: also reinfer if max_world != ~(size_t)0 jl_array_t *unspec = jl_alloc_vec_any(0); JL_GC_PUSH1(&unspec); jl_reset_mt_caches(jl_main_module, unspec); size_t i, l; for (i = 0, l = jl_array_len(unspec); i < l; i++) { jl_method_instance_t *li = (jl_method_instance_t*)jl_array_ptr_ref(unspec, i); - if (jl_is_uninferred(li)) - jl_type_infer(li, 1); + if (!jl_is_rettype_inferred(li)) + jl_type_infer(&li, jl_world_counter, 1); } JL_GC_POP(); } @@ -379,7 +558,8 @@ static jl_tupletype_t *join_tsig(jl_tupletype_t *tt, jl_tupletype_t *sig) } static jl_value_t *ml_matches(union jl_typemap_t ml, int offs, - jl_tupletype_t *type, int lim, int include_ambiguous); + jl_tupletype_t *type, int lim, int include_ambiguous, + size_t world, size_t *min_valid, size_t *max_valid); static void jl_cacheable_sig( jl_tupletype_t *const type, // the specialized type signature for type lambda @@ -619,6 +799,7 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t jl_tupletype_t *type, // the specialized type signature for type lambda jl_tupletype_t *tt, // the original tupletype of the signature jl_typemap_entry_t *m, + size_t world, jl_svec_t *sparams, int allow_exec) { @@ -706,6 +887,8 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t need_guard_entries = 1; } + size_t min_valid = definition->min_world; + size_t max_valid = definition->max_world; int cache_with_orig = 0; jl_svec_t* guardsigs = jl_emptysvec; jl_tupletype_t *origtype = type; // backup the prior value of `type` @@ -714,7 +897,7 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t temp2 = (jl_value_t*)type; } if (need_guard_entries) { - temp = ml_matches(mt->defs, 0, type, -1, 0); // TODO: use MAX_UNSPECIALIZED_CONFLICTS? + temp = ml_matches(mt->defs, 0, type, -1, 0, world, &min_valid, &max_valid); // TODO: use MAX_UNSPECIALIZED_CONFLICTS? int guards = 0; if (temp == jl_false) { cache_with_orig = 1; @@ -755,18 +938,23 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t guards = 0; for(i = 0, l = jl_array_len(temp); i < l; i++) { jl_value_t *m = jl_array_ptr_ref(temp, i); - if (((jl_method_t*)jl_svecref(m,2)) != definition) { + jl_method_t *other = (jl_method_t*)jl_svecref(m, 2); + if (other != definition) { jl_svecset(guardsigs, guards, (jl_tupletype_t*)jl_svecref(m, 0)); guards++; //jl_typemap_insert(cache, parent, (jl_tupletype_t*)jl_svecref(m, 0), - // jl_emptysvec, NULL, jl_emptysvec, /*guard*/NULL, jl_cachearg_offset(mt), &lambda_cache, NULL); + // jl_emptysvec, NULL, jl_emptysvec, /*guard*/NULL, jl_cachearg_offset(mt), &lambda_cache, other->min_world, other->max_world, NULL); } } } } // here we infer types and specialize the method - newmeth = jl_specializations_get_linfo(definition, type, sparams); + newmeth = jl_specializations_get_linfo(definition, type, sparams, world); + if (newmeth->min_world > min_valid) + min_valid = newmeth->min_world; + if (newmeth->max_world < max_valid) + max_valid = newmeth->max_world; if (cache_with_orig) { // if there is a need to cache with one of the original signatures, @@ -804,7 +992,8 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t } } - jl_typemap_insert(cache, parent, origtype, jl_emptysvec, type, guardsigs, (jl_value_t*)newmeth, jl_cachearg_offset(mt), &lambda_cache, NULL); + jl_typemap_insert(cache, parent, origtype, jl_emptysvec, type, guardsigs, (jl_value_t*)newmeth, jl_cachearg_offset(mt), &lambda_cache, + min_valid, max_valid, NULL); if (definition->traced && jl_method_tracer && allow_exec) jl_call_tracer(jl_method_tracer, (jl_value_t*)newmeth); @@ -812,7 +1001,7 @@ static jl_method_instance_t *cache_method(jl_methtable_t *mt, union jl_typemap_t return newmeth; } -static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, int cache, int inexact, int allow_exec) +static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype_t *tt, int cache, int inexact, int allow_exec, size_t world) { // caller must hold the mt->writelock jl_typemap_entry_t *entry = NULL; @@ -821,7 +1010,7 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype jl_tupletype_t *sig = NULL; JL_GC_PUSH4(&env, &entry, &func, &sig); - entry = jl_typemap_assoc_by_type(mt->defs, tt, &env, inexact, 1, 0); + entry = jl_typemap_assoc_by_type(mt->defs, tt, &env, inexact, 1, 0, world); if (entry == NULL || entry == INEXACT_ENTRY) { JL_GC_POP(); return NULL; @@ -835,10 +1024,10 @@ static jl_method_instance_t *jl_mt_assoc_by_type(jl_methtable_t *mt, jl_datatype sig = join_tsig(tt, entry->sig); jl_method_instance_t *nf; if (!cache) { - nf = jl_specializations_get_linfo(m, sig, env); + nf = jl_specializations_get_linfo(m, sig, env, world); } else { - nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, sig, tt, entry, env, allow_exec); + nf = cache_method(mt, &mt->cache, (jl_value_t*)mt, sig, tt, entry, world, env, allow_exec); } JL_GC_POP(); return nf; @@ -875,7 +1064,7 @@ struct ambiguous_matches_env { struct typemap_intersection_env match; union jl_typemap_t defs; jl_typemap_entry_t *newentry; - jl_array_t *shadowed; + jl_value_t *shadowed; int after; }; const int eager_ambiguity_printing = 0; @@ -906,7 +1095,8 @@ static int check_ambiguous_visitor(jl_typemap_entry_t *oldentry, struct typemap_ // now we are checking that the reverse is true if (!jl_args_morespecific((jl_value_t*)(closure->after ? type : sig), (jl_value_t*)(closure->after ? sig : type))) { - jl_typemap_entry_t *l = jl_typemap_assoc_by_type(map, (jl_tupletype_t*)isect, NULL, 0, 0, 0); + jl_typemap_entry_t *l = jl_typemap_assoc_by_type(map, (jl_tupletype_t*)isect, NULL, 0, 0, 0, + closure->newentry->min_world); if (l != NULL) // ok, intersection is covered return 1; jl_method_t *mambig = oldentry->func.method; @@ -937,14 +1127,22 @@ static int check_ambiguous_visitor(jl_typemap_entry_t *oldentry, struct typemap_ else if (closure->after) { // record that this method definition is being partially replaced if (closure->shadowed == NULL) { - closure->shadowed = jl_alloc_vec_any(0); + closure->shadowed = oldentry->func.value; + } + else if (!jl_is_array(closure->shadowed)) { + jl_array_t *list = jl_alloc_vec_any(2); + jl_array_ptr_set(list, 0, closure->shadowed); + jl_array_ptr_set(list, 1, oldentry->func.value); + closure->shadowed = (jl_value_t*)list; + } + else { + jl_array_ptr_1d_push((jl_array_t*)closure->shadowed, oldentry->func.value); } - jl_array_ptr_1d_push(closure->shadowed, oldentry->func.value); } return 1; } -static jl_array_t *check_ambiguous_matches(union jl_typemap_t defs, +static jl_value_t *check_ambiguous_matches(union jl_typemap_t defs, jl_typemap_entry_t *newentry) { jl_tupletype_t *type = newentry->sig; @@ -991,73 +1189,130 @@ static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue jl_printf(s, ".\n"); } -// invalidate cached methods that overlap this definition -static void flush_from_cache(jl_typemap_entry_t *entry); -static void invalidate_conflicting(union jl_typemap_t *pml, jl_value_t *type, jl_value_t *parent, jl_array_t *shadowed) -{ - jl_typemap_entry_t **pl; - if (jl_typeof(pml->unknown) == (jl_value_t*)jl_typemap_level_type) { - jl_typemap_level_t *cache = pml->node; - if (cache->arg1.values != (void*)jl_nothing) { - size_t i, l = jl_array_len(cache->arg1.values); - union jl_typemap_t *d = (union jl_typemap_t*)jl_array_data(cache->arg1.values); +static void update_max_args(jl_methtable_t *mt, jl_tupletype_t *type) +{ + size_t na = jl_nparams(type); + if (jl_va_tuple_kind(type) == JL_VARARG_UNBOUND) + na--; + if (na > mt->max_args) + mt->max_args = na; +} + + +// invalidate cached methods that had an edge to a replaced method +static void invalidate_method_instance(jl_method_instance_t *replaced, size_t max_world) +{ + JL_LOCK_NOGC(&replaced->def->writelock); + jl_array_t *backedges = replaced->backedges; + if (replaced->max_world > max_world) { + // recurse to all backedges to update their valid range also + assert(replaced->min_world <= max_world && "attempting to set invalid world constraints"); + replaced->max_world = max_world; + update_world_bound(replaced, set_max_world2, max_world); + if (backedges) { + size_t i, l = jl_array_len(backedges); for (i = 0; i < l; i++) { - union jl_typemap_t *pl = &d[i]; - if (pl->unknown != jl_nothing) { - invalidate_conflicting(pl, type, (jl_value_t*)cache->arg1.values, shadowed); - } + jl_method_instance_t *replaced = (jl_method_instance_t*)jl_array_ptr_ref(backedges, i); + invalidate_method_instance(replaced, max_world); } } - if (cache->targ.values != (void*)jl_nothing) { - size_t i, l = jl_array_len(cache->targ.values); - union jl_typemap_t *d = (union jl_typemap_t*)jl_array_data(cache->targ.values); + } + replaced->backedges = NULL; + JL_UNLOCK_NOGC(&replaced->def->writelock); +} + +// invalidate cached methods that overlap this definition +struct invalidate_conflicting_env { + struct typemap_intersection_env match; + size_t max_world; +}; +static int invalidate_backedges(jl_typemap_entry_t *oldentry, struct typemap_intersection_env *closure0) +{ + struct invalidate_conflicting_env *closure = container_of(closure0, struct invalidate_conflicting_env, match); + if (oldentry->max_world > closure->max_world) { + struct set_world def; + def.replaced = oldentry->func.linfo; + def.world = closure->max_world; + jl_method_t *m = def.replaced->def; + + // truncate the max-valid in the invoke cache + if (m->invokes.unknown != NULL) + jl_typemap_visitor(m->invokes, set_max_world2, (void*)&def); + // invalidate mt cache entries + jl_datatype_t *gf = jl_first_argument_datatype((jl_value_t*)m->sig); + assert(jl_is_datatype(gf) && gf->name->mt && "method signature invalid?"); + jl_typemap_visitor(gf->name->mt->cache, set_max_world2, (void*)&def); + + // invalidate backedges + JL_LOCK_NOGC(&def.replaced->def->writelock); + jl_array_t *backedges = def.replaced->backedges; + if (backedges) { + size_t i, l = jl_array_len(backedges); + jl_method_instance_t **replaced = (jl_method_instance_t**)jl_array_data(backedges); for (i = 0; i < l; i++) { - union jl_typemap_t *pl = &d[i]; - if (pl->unknown != jl_nothing) { - invalidate_conflicting(pl, type, (jl_value_t*)cache->targ.values, shadowed); - } + invalidate_method_instance(replaced[i], closure->max_world); } } - pl = &cache->linear; - parent = (jl_value_t*)cache; + def.replaced->backedges = NULL; + JL_UNLOCK_NOGC(&def.replaced->def->writelock); + } + return 1; +} + +// add a backedge from callee to caller +JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_method_instance_t *caller) +{ + assert(callee->min_world <= caller->min_world && callee->max_world >= caller->max_world); + JL_LOCK(&callee->def->writelock); + if (!callee->backedges) { + // lazy-init the backedges array + callee->backedges = jl_alloc_vec_any(1); + jl_gc_wb(callee, callee->backedges); + jl_array_ptr_set(callee->backedges, 0, caller); } else { - pl = &pml->leaf; - } - jl_typemap_entry_t *l = *pl; - size_t i, n = jl_array_len(shadowed); - jl_value_t **d = jl_array_ptr_data(shadowed); - while (l != (void*)jl_nothing) { - int replaced = 0; - for (i = 0; i < n; i++) { - if (d[i] == (jl_value_t*)l->func.linfo->def) { - replaced = jl_type_intersection(type, (jl_value_t*)l->sig) != (jl_value_t*)jl_bottom_type; + size_t i, l = jl_array_len(callee->backedges); + for (i = 0; i < l; i++) { + if (jl_array_ptr_ref(callee->backedges, i) == (jl_value_t*)caller) break; - } - } - if (replaced) { - flush_from_cache(l); - *pl = l->next; - jl_gc_wb(parent, *pl); } - else { - pl = &l->next; - parent = (jl_value_t*)l; + if (i == l) { + jl_array_ptr_1d_push(callee->backedges, (jl_value_t*)caller); } - l = l->next; } + JL_UNLOCK(&callee->def->writelock); } -static void update_max_args(jl_methtable_t *mt, jl_tupletype_t *type) +// add a backedge from a non-existent signature to caller +JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *typ, jl_value_t *caller) { - size_t na = jl_nparams(type); - if (jl_va_tuple_kind(type) == JL_VARARG_UNBOUND) - na--; - if (na > mt->max_args) - mt->max_args = na; + JL_LOCK(&mt->writelock); + if (!mt->backedges) { + // lazy-init the backedges array + mt->backedges = jl_alloc_vec_any(2); + jl_gc_wb(mt, mt->backedges); + jl_array_ptr_set(mt->backedges, 0, typ); + jl_array_ptr_set(mt->backedges, 1, caller); + } + else { + size_t i, l = jl_array_len(mt->backedges); + for (i = 1; i < l; i += 2) { + if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { + if (jl_array_ptr_ref(mt->backedges, i) == caller) { + JL_UNLOCK(&mt->writelock); + return; + } + // reuse the already cached instance of this type + typ = jl_array_ptr_ref(mt->backedges, i - 1); + } + } + jl_array_ptr_1d_push(mt->backedges, typ); + jl_array_ptr_1d_push(mt->backedges, caller); + } + JL_UNLOCK(&mt->writelock); } -void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype) +JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletype_t *simpletype) { assert(jl_is_method(method)); assert(jl_is_mtable(mt)); @@ -1065,42 +1320,89 @@ void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method, jl_tupletyp jl_svec_t *tvars = method->tvars; assert(jl_is_tuple_type(type)); jl_value_t *oldvalue = NULL; + struct invalidate_conflicting_env env; + env.max_world = method->min_world - 1; JL_GC_PUSH1(&oldvalue); JL_LOCK(&mt->writelock); jl_typemap_entry_t *newentry = jl_typemap_insert(&mt->defs, (jl_value_t*)mt, - type, tvars, simpletype, jl_emptysvec, (jl_value_t*)method, 0, &method_defs, &oldvalue); + type, tvars, simpletype, jl_emptysvec, (jl_value_t*)method, 0, &method_defs, + method->min_world, method->max_world, &oldvalue); if (oldvalue) { method->ambig = ((jl_method_t*)oldvalue)->ambig; method_overwrite(newentry, (jl_method_t*)oldvalue); - jl_array_t *shadowed = jl_alloc_vec_any(1); - jl_array_ptr_set(shadowed, 0, oldvalue); - oldvalue = (jl_value_t*)shadowed; } else { - oldvalue = (jl_value_t*)check_ambiguous_matches(mt->defs, newentry); + oldvalue = check_ambiguous_matches(mt->defs, newentry); + if (mt->backedges) { + jl_value_t **backedges = jl_array_data(mt->backedges); + size_t i, na = jl_array_len(mt->backedges); + size_t ins = 0; + for (i = 1; i < na; i += 2) { + jl_value_t *backedgetyp = backedges[i - 1]; + if (jl_type_intersection(backedgetyp, (jl_value_t*)type) != (jl_value_t*)jl_bottom_type) { + jl_method_instance_t *backedge = (jl_method_instance_t*)backedges[i]; + invalidate_method_instance(backedge, env.max_world); + } + else { + backedges[ins++] = backedges[i - 1]; + backedges[ins++] = backedges[i - 0]; + } + } + if (ins == 0) + mt->backedges = NULL; + else + jl_array_del_end(mt->backedges, na - ins); + } + } + if (oldvalue) { + size_t l = jl_svec_len(type->parameters); + jl_value_t *va = NULL; + if (l > 0) { + va = jl_tparam(type, l - 1); + if (jl_is_vararg_type(va)) + va = jl_tparam0(va); + else + va = NULL; + } + env.match.va = va; + env.match.type = (jl_value_t*)type; + env.match.fptr = invalidate_backedges; + + if (jl_is_method(oldvalue)) { + jl_typemap_intersection_visitor(((jl_method_t*)oldvalue)->specializations, 0, &env.match); + } + else { + assert(jl_is_array(oldvalue)); + jl_method_t **d = (jl_method_t**)jl_array_ptr_data(oldvalue); + size_t i, n = jl_array_len(oldvalue); + for (i = 0; i < n; i++) { + jl_typemap_intersection_visitor(d[i]->specializations, 0, &env.match); + } + } } - if (oldvalue) - invalidate_conflicting(&mt->cache, (jl_value_t*)type, (jl_value_t*)mt, (jl_array_t*)oldvalue); - JL_GC_POP(); update_max_args(mt, type); JL_UNLOCK(&mt->writelock); + JL_GC_POP(); } -void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args) +void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args, size_t world) { - jl_ptls_t ptls = jl_get_ptls_states(); - jl_value_t *fargs[3] = { - (jl_value_t*)jl_methoderror_type, - (jl_value_t*)f, - args - }; - if (fargs[0]) { - jl_throw(jl_apply_generic(fargs, 3)); + if (jl_methoderror_type) { + jl_value_t *e = jl_new_struct_uninit(jl_methoderror_type); + struct jl_method_error { + jl_value_t *f; + jl_value_t *args; + size_t world; + } *pe = (void*)e, + ee = {f, args, world}; + *pe = ee; + jl_throw(e); } else { jl_printf((JL_STREAM*)STDERR_FILENO, "A method error occurred before the base MethodError type was defined. Aborting...\n"); - jl_static_show((JL_STREAM*)STDERR_FILENO,(jl_value_t*)f); jl_printf((JL_STREAM*)STDERR_FILENO,"\n"); + jl_static_show((JL_STREAM*)STDERR_FILENO,(jl_value_t*)f); jl_printf((JL_STREAM*)STDERR_FILENO," world %u\n", (unsigned)world); jl_static_show((JL_STREAM*)STDERR_FILENO,args); jl_printf((JL_STREAM*)STDERR_FILENO,"\n"); + jl_ptls_t ptls = jl_get_ptls_states(); ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE); jl_critical_error(0, NULL, ptls->bt_data, &ptls->bt_size); abort(); @@ -1108,11 +1410,11 @@ void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args) // not reached } -void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na) +void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na, size_t world) { jl_value_t *argtup = jl_f_tuple(NULL, args+1, na-1); JL_GC_PUSH1(&argtup); - jl_method_error_bare(f, argtup); + jl_method_error_bare(f, argtup, world); // not reached } @@ -1152,20 +1454,27 @@ jl_tupletype_t *arg_type_tuple(jl_value_t **args, size_t nargs) } jl_method_instance_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_t *types, - int cache, int inexact, int allow_exec) + int cache, int inexact, int allow_exec, size_t world) { - jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt)); - if (entry) - return entry->func.linfo; + jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt), world); + if (entry) { + jl_method_instance_t *linfo = (jl_method_instance_t*)entry->func.value; + assert(linfo->min_world <= entry->min_world && linfo->max_world >= entry->max_world && + "typemap consistency error: MethodInstance doesn't apply to full range of its entry"); + return linfo; + } JL_LOCK(&mt->writelock); - entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt)); + entry = jl_typemap_assoc_by_type(mt->cache, types, NULL, 0, 1, jl_cachearg_offset(mt), world); if (entry) { + jl_method_instance_t *linfo = (jl_method_instance_t*)entry->func.value; + assert(linfo->min_world <= entry->min_world && linfo->max_world >= entry->max_world && + "typemap consistency error: MethodInstance doesn't apply to full range of its entry"); JL_UNLOCK(&mt->writelock); - return entry->func.linfo; + return linfo; } if (jl_is_leaf_type((jl_value_t*)types)) cache = 1; - jl_method_instance_t *sf = jl_mt_assoc_by_type(mt, types, cache, inexact, allow_exec); + jl_method_instance_t *sf = jl_mt_assoc_by_type(mt, types, cache, inexact, allow_exec, world); if (cache) { JL_UNLOCK(&mt->writelock); } @@ -1177,19 +1486,18 @@ jl_method_instance_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_ return sf; } -JL_DLLEXPORT int jl_method_exists(jl_methtable_t *mt, jl_tupletype_t *types) +JL_DLLEXPORT int jl_method_exists(jl_methtable_t *mt, jl_tupletype_t *types, size_t world) { - return jl_method_lookup_by_type(mt, types, 0, 0, 1) != NULL; + return jl_method_lookup_by_type(mt, types, 0, 0, 1, world) != NULL; } -jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache) +jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache, size_t world) { - jl_typemap_entry_t *entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); + jl_typemap_entry_t *entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt), world); if (entry) return entry->func.linfo; - JL_LOCK(&mt->writelock); - entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); + entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt), world); if (entry) { JL_UNLOCK(&mt->writelock); return entry->func.linfo; @@ -1197,7 +1505,7 @@ jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, si jl_tupletype_t *tt = arg_type_tuple(args, nargs); jl_method_instance_t *sf = NULL; JL_GC_PUSH2(&tt, &sf); - sf = jl_mt_assoc_by_type(mt, tt, cache, 0, 1); + sf = jl_mt_assoc_by_type(mt, tt, cache, 0, 1, world); if (cache) { JL_UNLOCK(&mt->writelock); } @@ -1210,10 +1518,36 @@ jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, si return sf; } -JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int include_ambiguous); +// return a Vector{Any} of svecs, each describing a method match: +// Any[svec(tt, spvals, m), ...] +// tt is the intersection of the type argument and the method signature, +// spvals is any matched static parameter values, m is the Method, +// +// lim is the max # of methods to return. if there are more, returns jl_false. +// -1 for no limit. +JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int include_ambiguous, size_t world, size_t *min_valid, size_t *max_valid) +{ + assert(jl_nparams(types) > 0); + jl_value_t *matches = NULL; + if (jl_tparam0(types) == jl_bottom_type) { + matches = (jl_value_t*)jl_alloc_vec_any(0); + } + else if (!jl_is_datatype(jl_tparam0(types))) { + return jl_false; // indeterminate - ml_matches can't deal with this case + } + else { + jl_methtable_t *mt = ((jl_datatype_t*)jl_tparam0(types))->name->mt; + if (mt == NULL) + matches = (jl_value_t*)jl_alloc_vec_any(0); + else + matches = ml_matches(mt->defs, 0, types, lim, include_ambiguous, world, min_valid, max_valid); + } + return matches; +} -jl_llvm_functions_t jl_compile_for_dispatch(jl_method_instance_t *li) +jl_llvm_functions_t jl_compile_for_dispatch(jl_method_instance_t **pli, size_t world) { + jl_method_instance_t *li = *pli; if (li->jlcall_api == 2) return li->functionObjectsDecls; if (jl_options.compile_enabled == JL_OPTIONS_COMPILE_OFF || @@ -1251,21 +1585,22 @@ jl_llvm_functions_t jl_compile_for_dispatch(jl_method_instance_t *li) return decls; jl_code_info_t *src = NULL; - if (li->def && jl_is_uninferred(li) && !li->inInference && + if (li->def && !jl_is_rettype_inferred(li) && !li->inInference && jl_symbol_name(li->def->name)[0] != '@') { // don't bother with typeinf on macros or toplevel thunks // but try to infer everything else - src = jl_type_infer(li, 0); + src = jl_type_infer(pli, world, 0); + li = *pli; } - // check again, because jl_type_infer may have compiled it + // check again, because jl_type_infer may have changed li or compiled it decls = li->functionObjectsDecls; if (decls.functionObject != NULL || li->jlcall_api == 2) return decls; - return jl_compile_linfo(li, src, &jl_default_cgparams); + return jl_compile_linfo(&li, src, world, &jl_default_cgparams); } // compile-time method lookup -jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types) +jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world) { JL_TIMING(METHOD_LOOKUP_COMPILE); assert(jl_nparams(types) > 0); @@ -1280,8 +1615,10 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types) // if one argument type is DataType, multiple Type{} definitions // might match. also be conservative with tuples rather than trying // to analyze them in detail. + size_t min_valid = 0; + size_t max_valid = ~(size_t)0; if (ti == (jl_value_t*)jl_datatype_type || jl_is_tuple_type(ti)) { - jl_value_t *matches = jl_matching_methods(types, 1, 0); + jl_value_t *matches = jl_matching_methods(types, 1, 0, world, &min_valid, &max_valid); if (matches == jl_false) return NULL; break; @@ -1292,7 +1629,8 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types) // most of the time sf is rooted in mt, but if the method is staged it may // not be the case // TODO: the above should be false, but better safe than sorry? - jl_method_instance_t *sf = jl_method_lookup_by_type(mt, types, 1, 1, 1); + jl_method_instance_t *sf = jl_method_lookup_by_type(mt, types, 1, 1, 1, world); + assert(sf == NULL || (sf->min_world <= world && sf->max_world >= world)); JL_GC_PUSH1(&sf); if (sf != NULL && jl_has_call_ambiguities(types, sf->def)) { sf = NULL; @@ -1303,20 +1641,21 @@ jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types) JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types) { - jl_method_instance_t *li = jl_get_specialization1(types); + size_t world = jl_world_counter; + jl_method_instance_t *li = jl_get_specialization1(types, world); if (li == NULL) return 0; jl_code_info_t *src = NULL; - if (jl_is_uninferred(li)) - src = jl_type_infer(li, 0); + if (!jl_is_rettype_inferred(li)) + src = jl_type_infer(&li, world, 0); if (li->jlcall_api != 2) - jl_compile_linfo(li, src, &jl_default_cgparams); + jl_compile_linfo(&li, src, world, &jl_default_cgparams); return 1; } -JL_DLLEXPORT jl_value_t *jl_get_spec_lambda(jl_tupletype_t *types) +JL_DLLEXPORT jl_value_t *jl_get_spec_lambda(jl_tupletype_t *types, size_t world) { - jl_method_instance_t *li = jl_get_specialization1(types); + jl_method_instance_t *li = jl_get_specialization1(types, world); return li ? (jl_value_t*)li : jl_nothing; } @@ -1527,18 +1866,17 @@ static void _compile_all_deq(jl_array_t *found) jl_method_t *m = ml->func.method; jl_method_instance_t *linfo = m->unspecialized; if (!linfo) { - // XXX: use computed env rather than empty svec - linfo = jl_specializations_get_linfo(m, ml->sig, jl_emptysvec); + linfo = jl_get_specialized(m, ml->sig, jl_emptysvec); m->unspecialized = linfo; jl_gc_wb(m, linfo); } - // infer this function now, if necessary - if (linfo->jlcall_api == 2) - continue; - src = jl_type_infer(linfo, 1); - if (linfo->jlcall_api == 2) - continue; + //// infer this function now, if necessary + //if (linfo->jlcall_api == 2) + // continue; + //src = jl_type_infer(&linfo, jl_world_counter, 1); + //if (linfo->jlcall_api == 2) + // continue; // keep track of whether all possible signatures have been cached (and thus whether it can skip trying to compile the template function) // this is necessary because many intrinsics try to call static_eval and thus are not compilable unspecialized @@ -1550,7 +1888,7 @@ static void _compile_all_deq(jl_array_t *found) linfo->fptr = (jl_fptr_t)(uintptr_t)-1; } else { - jl_compile_linfo(linfo, src, &jl_default_cgparams); + jl_compile_linfo(&linfo, src, jl_world_counter, &jl_default_cgparams); assert(linfo->functionObjectsDecls.functionObject != NULL); } } @@ -1757,15 +2095,6 @@ void call_cache_stats() { } #endif -static void flush_from_cache(jl_typemap_entry_t *entry) -{ - int i; - for (i = 0; i < N_CALL_CACHE; i++) { - if (call_cache[i] == entry) - call_cache[i] = NULL; - } -} - #ifdef _COMPILER_MICROSOFT_ #define __builtin_return_address(n) _ReturnAddress() #endif @@ -1780,6 +2109,7 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) if (traceen) show_call(args[0], &args[1], nargs-1); #endif + size_t world = jl_get_ptls_states()->world_age; /* search order: @@ -1808,7 +2138,8 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) for (i = 0; i < 4; i++) { entry = call_cache[cache_idx[i]]; if (entry && nargs == jl_svec_len(entry->sig->parameters) && - sig_match_fast(args, jl_svec_data(entry->sig->parameters), 0, nargs)) { + sig_match_fast(args, jl_svec_data(entry->sig->parameters), 0, nargs) && + world >= entry->min_world && world <= entry->max_world) { break; } } @@ -1817,7 +2148,7 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) JL_TIMING(METHOD_LOOKUP_FAST); jl_value_t *F = args[0]; mt = jl_gf_mtable(F); - entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); + entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt), world); if (entry && entry->isleafsig && entry->simplesig == (void*)jl_nothing && entry->guardsigs == jl_emptysvec) { // put the entry into the cache if it's valid for a leaftype lookup, // using pick_which to slightly randomize where it ends up @@ -1831,7 +2162,7 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) } else { JL_LOCK(&mt->writelock); - entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt)); + entry = jl_typemap_assoc_exact(mt->cache, args, nargs, jl_cachearg_offset(mt), world); if (entry) { mfunc = entry->func.linfo; } @@ -1840,7 +2171,7 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) JL_TIMING(METHOD_LOOKUP_SLOW); jl_tupletype_t *tt = arg_type_tuple(args, nargs); JL_GC_PUSH1(&tt); - mfunc = jl_mt_assoc_by_type(mt, tt, 1, 0, 1); + mfunc = jl_mt_assoc_by_type(mt, tt, 1, 0, 1, world); JL_GC_POP(); } JL_UNLOCK(&mt->writelock); @@ -1849,7 +2180,7 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) if (error_en) show_call(F, args, nargs); #endif - jl_method_error((jl_function_t*)args[0], args, nargs); + jl_method_error((jl_function_t*)args[0], args, nargs, world); // unreachable } } @@ -1862,11 +2193,11 @@ JL_DLLEXPORT jl_value_t *jl_apply_generic(jl_value_t **args, uint32_t nargs) return verify_type(res); } -JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_datatype_t *types) +JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_datatype_t *types, size_t world) { jl_methtable_t *mt = ((jl_datatype_t*)jl_tparam0(types))->name->mt; jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(mt->defs, types, /*don't record env*/NULL, - /*exact match*/0, /*subtype*/1, /*offs*/0); + /*exact match*/0, /*subtype*/1, /*offs*/0, world); if (!entry) return jl_nothing; return (jl_value_t*)entry; @@ -1883,6 +2214,7 @@ JL_DLLEXPORT jl_value_t *jl_gf_invoke_lookup(jl_datatype_t *types) // NOTE: assumes argument type is a subtype of the lookup type. jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs) { + size_t world = jl_get_ptls_states()->world_age; jl_svec_t *tpenv = jl_emptysvec; jl_tupletype_t *newsig = NULL; jl_tupletype_t *tt = NULL; @@ -1892,10 +2224,10 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs jl_value_t *gf = args[0]; types = (jl_datatype_t*)jl_argtype_with_function(gf, (jl_tupletype_t*)types0); jl_methtable_t *mt = jl_gf_mtable(gf); - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_gf_invoke_lookup(types); + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_gf_invoke_lookup(types, world); if ((jl_value_t*)entry == jl_nothing) { - jl_method_error_bare(gf, (jl_value_t*)types0); + jl_method_error_bare(gf, (jl_value_t*)types0, world); // unreachable } @@ -1906,14 +2238,14 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs jl_method_instance_t *mfunc = NULL; jl_typemap_entry_t *tm = NULL; if (method->invokes.unknown != NULL) - tm = jl_typemap_assoc_exact(method->invokes, args, nargs, jl_cachearg_offset(mt)); + tm = jl_typemap_assoc_exact(method->invokes, args, nargs, jl_cachearg_offset(mt), world); if (tm) { mfunc = tm->func.linfo; } else { JL_LOCK(&method->writelock); if (method->invokes.unknown != NULL) - tm = jl_typemap_assoc_exact(method->invokes, args, nargs, jl_cachearg_offset(mt)); + tm = jl_typemap_assoc_exact(method->invokes, args, nargs, jl_cachearg_offset(mt), world); if (tm) { mfunc = tm->func.linfo; } @@ -1931,7 +2263,7 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs if (func->invokes.unknown == NULL) func->invokes.unknown = jl_nothing; - mfunc = cache_method(mt, &func->invokes, entry->func.value, sig, tt, entry, tpenv, 1); + mfunc = cache_method(mt, &func->invokes, entry->func.value, sig, tt, entry, world, tpenv, 1); } JL_UNLOCK(&method->writelock); } @@ -2025,8 +2357,14 @@ static int tvar_exists_at_top_level(jl_value_t *tv, jl_tupletype_t *sig, int att struct ml_matches_env { struct typemap_intersection_env match; - jl_value_t *t; // results: array of svec(argtypes, params, Method) + // results: + jl_value_t *t; // array of svec(argtypes, params, Method) + size_t min_valid; + size_t max_valid; + // temporary: jl_svec_t *matc; // current working svec + // inputs: + size_t world; int lim; int include_ambiguous; // whether ambiguous matches should be included }; @@ -2034,6 +2372,27 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio { struct ml_matches_env *closure = container_of(closure0, struct ml_matches_env, match); int i; + if (closure->world != 0) { // use zero as a flag value for returning all matches + // ignore method table entries that have been replaced in the current world + if (closure->world < ml->min_world) { + if (closure->max_valid >= ml->min_world) + closure->max_valid = ml->min_world - 1; + return 1; + } + else if (closure->world > ml->max_world) { + // ignore method table entries that are part of a later world + if (closure->min_valid <= ml->max_world) + closure->min_valid = ml->max_world + 1; + return 1; + } + else { + // intersect the env valid range with method's valid range + if (closure->min_valid < ml->min_world) + closure->min_valid = ml->min_world; + if (closure->max_valid > ml->max_world) + closure->max_valid = ml->max_world; + } + } // a method is shadowed if type <: S <: m->sig where S is the // signature of another applicable method /* @@ -2173,7 +2532,8 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio // Returns a match as an array of svec(argtypes, static_params, Method). // See below for the meaning of lim. static jl_value_t *ml_matches(union jl_typemap_t defs, int offs, - jl_tupletype_t *type, int lim, int include_ambiguous) + jl_tupletype_t *type, int lim, int include_ambiguous, + size_t world, size_t *min_valid, size_t *max_valid) { size_t l = jl_svec_len(type->parameters); jl_value_t *va = NULL; @@ -2194,31 +2554,17 @@ static jl_value_t *ml_matches(union jl_typemap_t defs, int offs, env.matc = NULL; env.lim = lim; env.include_ambiguous = include_ambiguous; + env.world = world; + env.min_valid = *min_valid; + env.max_valid = *max_valid; JL_GC_PUSH4(&env.t, &env.matc, &env.match.env, &env.match.ti); jl_typemap_intersection_visitor(defs, offs, &env.match); JL_GC_POP(); + *min_valid = env.min_valid; + *max_valid = env.max_valid; return env.t; } -// return a Vector{Any} of svecs, each describing a method match: -// Any[svec(tt, spvals, m), ...] -// tt is the intersection of the type argument and the method signature, -// spvals is any matched static parameter values, m is the Method, -// -// lim is the max # of methods to return. if there are more, returns jl_false. -// -1 for no limit. -JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, int lim, int include_ambiguous) -{ - assert(jl_nparams(types) > 0); - if (jl_tparam0(types) == jl_bottom_type) - return (jl_value_t*)jl_alloc_vec_any(0); - assert(jl_is_datatype(jl_tparam0(types))); - jl_methtable_t *mt = ((jl_datatype_t*)jl_tparam0(types))->name->mt; - if (mt == NULL) - return (jl_value_t*)jl_alloc_vec_any(0); - return ml_matches(mt->defs, 0, types, lim, include_ambiguous); -} - // TODO: separate the codegen and typeinf locks // currently using a coarser lock seems like // the best way to avoid acquisition priority diff --git a/src/init.c b/src/init.c index bffa565d11b60..b49703c0be759 100644 --- a/src/init.c +++ b/src/init.c @@ -203,7 +203,10 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("_atexit")); if (f != NULL) { JL_TRY { + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); jl_apply(&f, 1); + jl_get_ptls_states()->world_age = last_age; } JL_CATCH { jl_printf(JL_STDERR, "\natexit hook threw an error: "); diff --git a/src/interpreter.c b/src/interpreter.c index d84191d90f5c3..0c3f9e7382032 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -29,7 +29,11 @@ int jl_is_toplevel_only_expr(jl_value_t *e); jl_value_t *jl_interpret_toplevel_expr(jl_value_t *e) { - return eval(e, NULL); + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_world_counter; + jl_value_t *ret = eval(e, NULL); + jl_get_ptls_states()->world_age = last_age; + return ret; } jl_value_t *jl_interpret_toplevel_expr_in(jl_module_t *m, jl_value_t *e, @@ -480,7 +484,10 @@ static jl_value_t *eval(jl_value_t *e, interpreter_state *s) jl_value_t *jl_toplevel_eval_body(jl_array_t *stmts) { - return eval_body(stmts, NULL, 0, 1); + size_t last_age = jl_get_ptls_states()->world_age; + jl_value_t *ret = eval_body(stmts, NULL, 0, 1); + jl_get_ptls_states()->world_age = last_age; + return ret; } static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, int start, int toplevel) @@ -492,15 +499,17 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, int start, while (1) { if (i >= ns) jl_error("`body` expression must terminate in `return`. Use `block` instead."); - jl_value_t *stmt = jl_array_ptr_ref(stmts,i); + if (toplevel) + jl_get_ptls_states()->world_age = jl_world_counter; + jl_value_t *stmt = jl_array_ptr_ref(stmts, i); if (jl_is_gotonode(stmt)) { - i = jl_gotonode_label(stmt)-1; + i = jl_gotonode_label(stmt) - 1; continue; } else if (jl_is_expr(stmt)) { jl_sym_t *head = ((jl_expr_t*)stmt)->head; if (head == return_sym) { - jl_value_t *ex = jl_exprarg(stmt,0); + jl_value_t *ex = jl_exprarg(stmt, 0); if (toplevel && jl_is_toplevel_only_expr(ex)) return jl_toplevel_eval(ex); else @@ -508,7 +517,7 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, int start, } else if (head == assign_sym) { jl_value_t *sym = jl_exprarg(stmt, 0); - jl_value_t *rhs = eval(jl_exprarg(stmt,1), s); + jl_value_t *rhs = eval(jl_exprarg(stmt, 1), s); if (jl_is_ssavalue(sym)) { ssize_t genid = ((jl_ssavalue_t*)sym)->id; if (genid >= jl_source_nssavalues(s->src) || genid < 0) @@ -537,9 +546,9 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, int start, } } else if (head == goto_ifnot_sym) { - jl_value_t *cond = eval(jl_exprarg(stmt,0), s); + jl_value_t *cond = eval(jl_exprarg(stmt, 0), s); if (cond == jl_false) { - i = jl_unbox_long(jl_exprarg(stmt, 1))-1; + i = jl_unbox_long(jl_exprarg(stmt, 1)) - 1; continue; } else if (cond != jl_true) { @@ -548,20 +557,20 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, int start, } else if (head == line_sym) { if (toplevel) - jl_lineno = jl_unbox_long(jl_exprarg(stmt,0)); + jl_lineno = jl_unbox_long(jl_exprarg(stmt, 0)); // TODO: interpreted function line numbers } else if (head == enter_sym) { jl_enter_handler(&__eh); if (!jl_setjmp(__eh.eh_ctx,1)) { - return eval_body(stmts, s, i+1, toplevel); + return eval_body(stmts, s, i + 1, toplevel); } else { #ifdef _OS_WINDOWS_ if (ptls->exception_in_transit == jl_stackovf_exception) _resetstkoflw(); #endif - i = jl_unbox_long(jl_exprarg(stmt,0))-1; + i = jl_unbox_long(jl_exprarg(stmt, 0)) - 1; continue; } } @@ -582,11 +591,11 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, int start, // TODO: interpreted function line numbers } else if (jl_is_newvarnode(stmt)) { - jl_value_t *var = jl_fieldref(stmt,0); + jl_value_t *var = jl_fieldref(stmt, 0); assert(jl_is_slot(var)); ssize_t n = jl_slot_number(var); assert(n <= jl_source_nslots(s->src) && n > 0); - s->locals[n-1] = NULL; + s->locals[n - 1] = NULL; } else { eval(stmt, s); @@ -652,7 +661,9 @@ jl_value_t *jl_interpret_toplevel_thunk(jl_code_info_t *src) s.locals = locals; s.module = ptls->current_module; s.sparam_vals = jl_emptysvec; + size_t last_age = jl_get_ptls_states()->world_age; jl_value_t *r = eval_body(stmts, &s, 0, 1); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); return r; } diff --git a/src/jl_uv.c b/src/jl_uv.c index 2c9f312ae682a..6c8bbe3e90007 100644 --- a/src/jl_uv.c +++ b/src/jl_uv.c @@ -100,8 +100,12 @@ static void jl_uv_closeHandle(uv_handle_t *handle) if (handle == (uv_handle_t*)JL_STDERR) JL_STDERR = (JL_STREAM*)STDERR_FILENO; // also let the client app do its own cleanup - if (handle->type != UV_FILE && handle->data) + if (handle->type != UV_FILE && handle->data) { + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_world_counter; jl_uv_call_close_callback((jl_value_t*)handle->data); + jl_get_ptls_states()->world_age = last_age; + } if (handle == (uv_handle_t*)&signal_async) return; free(handle); diff --git a/src/jlapi.c b/src/jlapi.c index 7f02c82b4c7ad..06d7920d59835 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -59,7 +59,10 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) jl_value_t *ast = jl_parse_input_line(str, strlen(str), filename, strlen(filename)); JL_GC_PUSH1(&ast); + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); r = jl_toplevel_eval(ast); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); jl_exception_clear(); } @@ -126,7 +129,10 @@ JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t na argv[0] = (jl_value_t*)f; for(int i=1; iworld_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); v = jl_apply(argv, nargs+1); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); jl_exception_clear(); } @@ -141,7 +147,10 @@ JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f) jl_value_t *v; JL_TRY { JL_GC_PUSH1(&f); + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); v = jl_apply(&f, 1); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); jl_exception_clear(); } @@ -158,7 +167,10 @@ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f, jl_value_t *a) jl_value_t **argv; JL_GC_PUSHARGS(argv, 2); argv[0] = f; argv[1] = a; + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); v = jl_apply(argv, 2); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); jl_exception_clear(); } @@ -175,7 +187,10 @@ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b jl_value_t **argv; JL_GC_PUSHARGS(argv, 3); argv[0] = f; argv[1] = a; argv[2] = b; + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); v = jl_apply(argv, 3); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); jl_exception_clear(); } @@ -193,7 +208,10 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, jl_value_t **argv; JL_GC_PUSHARGS(argv, 4); argv[0] = f; argv[1] = a; argv[2] = b; argv[3] = c; + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); v = jl_apply(argv, 4); + jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); jl_exception_clear(); } diff --git a/src/jltypes.c b/src/jltypes.c index e70a22a0809df..ef7a7ff0e4c2e 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3600,12 +3600,13 @@ void jl_init_types(void) jl_methtable_type->name->mt = jl_new_method_table(jl_methtable_type->name->name, ptls->current_module); jl_methtable_type->super = jl_any_type; jl_methtable_type->parameters = jl_emptysvec; - jl_methtable_type->name->names = jl_svec(8, jl_symbol("name"), jl_symbol("defs"), + jl_methtable_type->name->names = jl_svec(9, jl_symbol("name"), jl_symbol("defs"), jl_symbol("cache"), jl_symbol("max_args"), jl_symbol("kwsorter"), jl_symbol("module"), - jl_symbol(""), jl_symbol("")); - jl_methtable_type->types = jl_svec(8, jl_sym_type, jl_any_type, jl_any_type, jl_any_type/*jl_long*/, - jl_any_type, jl_any_type/*module*/, jl_any_type/*long*/, jl_any_type/*int32*/); + jl_symbol("backedges"), jl_symbol(""), jl_symbol("")); + jl_methtable_type->types = jl_svec(9, jl_sym_type, jl_any_type, jl_any_type, jl_any_type/*jl_long*/, + jl_any_type, jl_any_type/*module*/, + jl_any_type/*any vector*/, jl_any_type/*long*/, jl_any_type/*int32*/); jl_methtable_type->uid = jl_assign_type_uid(); jl_methtable_type->instance = NULL; jl_methtable_type->struct_decl = NULL; @@ -3751,24 +3752,30 @@ void jl_init_types(void) jl_typemap_entry_type = jl_new_datatype(jl_symbol("TypeMapEntry"), jl_any_type, jl_emptysvec, - jl_svec(9, jl_symbol("next"), - jl_symbol("sig"), - jl_symbol("tvars"), - jl_symbol("simplesig"), - jl_symbol("guardsigs"), - jl_symbol("func"), - jl_symbol("isleafsig"), - jl_symbol("issimplesig"), - jl_symbol("va")), - jl_svec(9, jl_any_type, // Union{TypeMapEntry, Void} - jl_type_type, // TupleType - jl_any_type, // Union{SimpleVector{TypeVar}, TypeVar} - jl_any_type, // TupleType - jl_any_type, // SimpleVector{TupleType} - jl_any_type, // Any - jl_bool_type, - jl_bool_type, - jl_bool_type), + jl_svec(11, + jl_symbol("next"), + jl_symbol("sig"), + jl_symbol("tvars"), + jl_symbol("simplesig"), + jl_symbol("guardsigs"), + jl_symbol("min_world"), + jl_symbol("max_world"), + jl_symbol("func"), + jl_symbol("isleafsig"), + jl_symbol("issimplesig"), + jl_symbol("va")), + jl_svec(11, + jl_any_type, // Union{TypeMapEntry, Void} + jl_type_type, // TupleType + jl_any_type, // Union{SimpleVector{TypeVar}, TypeVar} + jl_any_type, // TupleType + jl_any_type, // SimpleVector{TupleType} + jl_long_type, // Int + jl_long_type, // Int + jl_any_type, // Any + jl_bool_type, + jl_bool_type, + jl_bool_type), 0, 1, 5); jl_function_type = jl_new_abstracttype((jl_value_t*)jl_symbol("Function"), jl_any_type, jl_emptysvec); @@ -3854,9 +3861,6 @@ void jl_init_types(void) jl_svec(2, jl_symbol("mod"), jl_symbol("name")), jl_svec(2, jl_module_type, jl_sym_type), 0, 0, 2); - jl_svecset(jl_typename_type->types, 1, jl_module_type); - jl_svecset(jl_methtable_type->types, 5, jl_module_type); - jl_code_info_type = jl_new_datatype(jl_symbol("CodeInfo"), jl_any_type, jl_emptysvec, @@ -3885,13 +3889,15 @@ void jl_init_types(void) jl_method_type = jl_new_datatype(jl_symbol("Method"), jl_any_type, jl_emptysvec, - jl_svec(18, + jl_svec(20, jl_symbol("name"), jl_symbol("module"), jl_symbol("file"), jl_symbol("line"), jl_symbol("sig"), jl_symbol("tvars"), + jl_symbol("min_world"), + jl_symbol("max_world"), jl_symbol("ambig"), jl_symbol("specializations"), jl_symbol("sparam_syms"), @@ -3904,15 +3910,17 @@ void jl_init_types(void) jl_symbol("isva"), jl_symbol("isstaged"), jl_symbol("needs_sparam_vals_ducttape")), - jl_svec(18, + jl_svec(20, jl_sym_type, jl_module_type, jl_sym_type, jl_int32_type, jl_type_type, - jl_any_type, + jl_any_type, // Union{TypeVar, SimpleVector} + jl_long_type, + jl_long_type, jl_any_type, // Union{Array, Void} - jl_any_type, + jl_any_type, // TypeMap jl_simplevector_type, jl_code_info_type, jl_any_type, // jl_method_instance_type @@ -3923,31 +3931,37 @@ void jl_init_types(void) jl_bool_type, jl_bool_type, jl_bool_type), - 0, 1, 9); + 0, 1, 11); jl_method_instance_type = jl_new_datatype(jl_symbol("MethodInstance"), jl_any_type, jl_emptysvec, - jl_svec(13, + jl_svec(16, jl_symbol("specTypes"), jl_symbol("rettype"), jl_symbol("sparam_vals"), + jl_symbol("backedges"), jl_symbol("inferred"), jl_symbol("inferred_const"), jl_symbol("def"), + jl_symbol("min_world"), + jl_symbol("max_world"), jl_symbol("inInference"), jl_symbol("jlcall_api"), jl_symbol(""), jl_symbol("fptr"), jl_symbol("unspecialized_ducttape"), jl_symbol(""), jl_symbol("")), - jl_svec(13, + jl_svec(16, jl_any_type, jl_any_type, jl_simplevector_type, jl_any_type, jl_any_type, + jl_any_type, jl_method_type, + jl_long_type, + jl_long_type, jl_bool_type, jl_uint8_type, jl_bool_type, @@ -4007,19 +4021,22 @@ void jl_init_types(void) jl_svecset(jl_datatype_type->types, 16, jl_bool_type); jl_svecset(jl_tvar_type->types, 3, jl_bool_type); jl_svecset(jl_simplevector_type->types, 0, jl_long_type); + jl_svecset(jl_typename_type->types, 1, jl_module_type); jl_svecset(jl_typename_type->types, 6, jl_long_type); jl_svecset(jl_methtable_type->types, 3, jl_long_type); + jl_svecset(jl_methtable_type->types, 5, jl_module_type); + jl_svecset(jl_methtable_type->types, 6, jl_array_any_type); #ifdef __LP64__ - jl_svecset(jl_methtable_type->types, 6, jl_int64_type); // unsigned long + jl_svecset(jl_methtable_type->types, 7, jl_int64_type); // unsigned long #else - jl_svecset(jl_methtable_type->types, 6, jl_int32_type); // DWORD + jl_svecset(jl_methtable_type->types, 7, jl_int32_type); // DWORD #endif - jl_svecset(jl_methtable_type->types, 7, jl_int32_type); // uint32_t - jl_svecset(jl_method_type->types, 10, jl_method_instance_type); - jl_svecset(jl_method_instance_type->types, 9, jl_voidpointer_type); - jl_svecset(jl_method_instance_type->types, 10, jl_voidpointer_type); - jl_svecset(jl_method_instance_type->types, 11, jl_voidpointer_type); + jl_svecset(jl_methtable_type->types, 8, jl_int32_type); // uint32_t + jl_svecset(jl_method_type->types, 12, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 12, jl_voidpointer_type); + jl_svecset(jl_method_instance_type->types, 13, jl_voidpointer_type); + jl_svecset(jl_method_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_method_instance_type->types, 15, jl_voidpointer_type); jl_compute_field_offsets(jl_datatype_type); jl_compute_field_offsets(jl_typename_type); diff --git a/src/julia.h b/src/julia.h index c767ae4e094e2..1e85fb092f11a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -230,8 +230,11 @@ typedef struct _jl_method_t { // method's type signature. redundant with TypeMapEntry->specTypes jl_tupletype_t *sig; - // bound type variables (static parameters). redundant with TypeMapEntry->tvars + // bound type variables (static parameters) jl_svec_t *tvars; + size_t min_world; + size_t max_world; + // list of potentially-ambiguous methods (nothing = none, Vector{Any} of Methods otherwise) jl_value_t *ambig; @@ -272,9 +275,12 @@ typedef struct _jl_method_instance_t { jl_tupletype_t *specTypes; // argument types this was specialized for jl_value_t *rettype; // return type for fptr jl_svec_t *sparam_vals; // the values for the tvars, indexed by def->sparam_syms + jl_array_t *backedges; jl_value_t *inferred; // inferred jl_code_info_t, or value of the function if jlcall_api == 2, or null jl_value_t *inferred_const; // inferred constant return value, or null jl_method_t *def; // method this is specialized from, null if this is a toplevel thunk + size_t min_world; + size_t max_world; uint8_t inInference; // flags to tell if inference is running on this function uint8_t jlcall_api; // the c-abi for fptr; 0 = jl_fptr_t, 1 = jl_fptr_sparam_t, 2 = constval uint8_t compile_traced; // if set will notify callback if this linfo is compiled @@ -423,6 +429,8 @@ typedef struct _jl_typemap_entry_t { jl_svec_t *tvars; // the bound type variables for sig jl_tupletype_t *simplesig; // a simple signature for fast rejection jl_svec_t *guardsigs; + size_t min_world; + size_t max_world; union { jl_value_t *value; jl_method_instance_t *linfo; // [nullable] for guard entries @@ -458,6 +466,7 @@ typedef struct _jl_methtable_t { intptr_t max_args; // max # of non-vararg arguments in a signature jl_value_t *kwsorter; // keyword argument sorter function jl_module_t *module; // used for incremental serialization to locate original binding + jl_array_t *backedges; jl_mutex_t writelock; } jl_methtable_t; @@ -1016,7 +1025,7 @@ JL_DLLEXPORT jl_svec_t *jl_svec_fill(size_t n, jl_value_t *x); JL_DLLEXPORT jl_value_t *jl_tupletype_fill(size_t n, jl_value_t *v); JL_DLLEXPORT jl_sym_t *jl_symbol(const char *str); JL_DLLEXPORT jl_sym_t *jl_symbol_lookup(const char *str); -JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, int32_t len); +JL_DLLEXPORT jl_sym_t *jl_symbol_n(const char *str, size_t len); JL_DLLEXPORT jl_sym_t *jl_gensym(void); JL_DLLEXPORT jl_sym_t *jl_tagged_gensym(const char *str, int32_t len); JL_DLLEXPORT jl_sym_t *jl_get_root_symbol(void); @@ -1026,6 +1035,7 @@ JL_DLLEXPORT jl_value_t *jl_generic_function_def(jl_sym_t *name, jl_value_t **bp JL_DLLEXPORT void jl_method_def(jl_svec_t *argdata, jl_code_info_t *f, jl_value_t *isstaged); JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo); JL_DLLEXPORT jl_code_info_t *jl_copy_code_info(jl_code_info_t *src); +JL_DLLEXPORT size_t jl_get_world_counter(void); JL_DLLEXPORT jl_function_t *jl_get_kwsorter(jl_typename_t *tn); JL_DLLEXPORT jl_value_t *jl_box_bool(int8_t x); JL_DLLEXPORT jl_value_t *jl_box_int8(int8_t x); @@ -1409,6 +1419,7 @@ typedef struct _jl_handler_t { sig_atomic_t defer_signal; int finalizers_inhibited; jl_timing_block_t *timing_stack; + size_t world_age; } jl_handler_t; typedef struct _jl_task_t { @@ -1426,6 +1437,7 @@ typedef struct _jl_task_t { size_t bufsz; void *stkbuf; +// hidden fields: size_t ssize; size_t started:1; @@ -1435,6 +1447,8 @@ typedef struct _jl_task_t { jl_gcframe_t *gcstack; // current module, or NULL if this task has not set one jl_module_t *current_module; + // current world age + size_t world_age; // id of owning thread // does not need to be defined until the task runs @@ -1506,6 +1520,7 @@ STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) locks->len = eh->locks_len; } #endif + ptls->world_age = eh->world_age; ptls->defer_signal = eh->defer_signal; ptls->gc_state = eh->gc_state; ptls->finalizers_inhibited = eh->finalizers_inhibited; diff --git a/src/julia_internal.h b/src/julia_internal.h index 368331f0da74d..f6323daac6544 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -39,12 +39,14 @@ extern "C" { // useful constants extern jl_methtable_t *jl_type_type_mt; +extern size_t jl_world_counter; typedef void (*tracer_cb)(jl_value_t *tracee); void jl_call_tracer(tracer_cb callback, jl_value_t *tracee); extern size_t jl_page_size; extern jl_function_t *jl_typeinf_func; +extern size_t jl_typeinf_world; JL_DLLEXPORT extern int jl_lineno; JL_DLLEXPORT extern const char *jl_filename; @@ -190,10 +192,10 @@ STATIC_INLINE void *jl_gc_alloc_buf(jl_ptls_t ptls, size_t sz) return jl_gc_alloc(ptls, sz, (void*)jl_buff_tag); } -jl_code_info_t *jl_type_infer(jl_method_instance_t *li, int force); -jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, void *F); -jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t *li, jl_code_info_t *src, const jl_cgparams_t *params); -jl_llvm_functions_t jl_compile_for_dispatch(jl_method_instance_t *li); +jl_code_info_t *jl_type_infer(jl_method_instance_t **li, size_t world, int force); +jl_generic_fptr_t jl_generate_fptr(jl_method_instance_t *li, void *F, size_t world); +jl_llvm_functions_t jl_compile_linfo(jl_method_instance_t **pli, jl_code_info_t *src, size_t world, const jl_cgparams_t *params); +jl_llvm_functions_t jl_compile_for_dispatch(jl_method_instance_t **li, size_t world); JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types); jl_code_info_t *jl_new_code_info_from_ast(jl_expr_t *ast); jl_method_t *jl_new_method(jl_code_info_t *definition, @@ -213,10 +215,11 @@ STATIC_INLINE jl_value_t *jl_call_method_internal(jl_method_instance_t *meth, jl if (fptr.jlcall_api == 2) return meth->inferred; if (__unlikely(fptr.fptr == NULL || fptr.jlcall_api == 0)) { + size_t world = jl_get_ptls_states()->world_age; // first see if it likely needs to be compiled void *F = meth->functionObjectsDecls.functionObject; if (!F) // ask codegen to try to turn it into llvm code - F = jl_compile_for_dispatch(meth).functionObject; + F = jl_compile_for_dispatch(&meth, world).functionObject; if (meth->jlcall_api == 2) return meth->inferred; // if it hasn't been inferred, try using the unspecialized meth cache instead @@ -234,7 +237,7 @@ STATIC_INLINE jl_value_t *jl_call_method_internal(jl_method_instance_t *meth, jl } if (!fptr.fptr || fptr.jlcall_api == 0) { // ask codegen to make the fptr - fptr = jl_generate_fptr(meth, F); + fptr = jl_generate_fptr(meth, F, world); if (fptr.jlcall_api == 2) return meth->inferred; } @@ -290,8 +293,8 @@ void jl_set_t_uid_ctr(int i); uint32_t jl_get_gs_ctr(void); void jl_set_gs_ctr(uint32_t ctr); -void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args); -void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na); +void JL_NORETURN jl_method_error_bare(jl_function_t *f, jl_value_t *args, size_t world); +void JL_NORETURN jl_method_error(jl_function_t *f, jl_value_t **args, size_t na, size_t world); jl_value_t *jl_get_exceptionf(jl_datatype_t *exception_type, const char *fmt, ...); JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t); @@ -363,11 +366,11 @@ int jl_is_toplevel_only_expr(jl_value_t *e); jl_value_t *jl_call_scm_on_ast(const char *funcname, jl_value_t *expr); jl_method_instance_t *jl_method_lookup_by_type(jl_methtable_t *mt, jl_tupletype_t *types, - int cache, int inexact, int allow_exec); -jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache); + int cache, int inexact, int allow_exec, size_t world); +jl_method_instance_t *jl_method_lookup(jl_methtable_t *mt, jl_value_t **args, size_t nargs, int cache, size_t world); jl_value_t *jl_gf_invoke(jl_tupletype_t *types, jl_value_t **args, size_t nargs); -jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes); +JL_DLLEXPORT jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes); int jl_has_intrinsics(jl_method_instance_t *li, jl_value_t *v, jl_module_t *m); jl_value_t *jl_nth_slot_type(jl_tupletype_t *sig, size_t i); @@ -479,11 +482,13 @@ int32_t jl_jlcall_api(/*llvm::Function*/const void *function); JL_DLLEXPORT jl_array_t *jl_idtable_rehash(jl_array_t *a, size_t newsz); JL_DLLEXPORT jl_methtable_t *jl_new_method_table(jl_sym_t *name, jl_module_t *module); -jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types); +jl_method_instance_t *jl_get_specialization1(jl_tupletype_t *types, size_t world); JL_DLLEXPORT int jl_has_call_ambiguities(jl_tupletype_t *types, jl_method_t *m); jl_method_instance_t *jl_get_specialized(jl_method_t *m, jl_tupletype_t *types, jl_svec_t *sp); -JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_tupletype_t *type); -JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m, jl_tupletype_t *type, jl_svec_t *sparams); +JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_tupletype_t *type, size_t world); +JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m, jl_tupletype_t *type, jl_svec_t *sparams, size_t world); +JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_method_instance_t *caller); +JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *typ, jl_value_t *caller); uint32_t jl_module_next_counter(jl_module_t *m); void jl_fptr_to_llvm(jl_fptr_t fptr, jl_method_instance_t *lam, int specsig); @@ -764,21 +769,22 @@ jl_typemap_entry_t *jl_typemap_insert(union jl_typemap_t *cache, jl_value_t *par jl_tupletype_t *simpletype, jl_svec_t *guardsigs, jl_value_t *newvalue, int8_t offs, const struct jl_typemap_info *tparams, + size_t min_world, size_t max_world, jl_value_t **overwritten); jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_tupletype_t *types, jl_svec_t **penv, - int8_t subtype_inexact__sigseq_useenv, int8_t subtype, int8_t offs); + int8_t subtype_inexact__sigseq_useenv, int8_t subtype, int8_t offs, size_t world); static jl_typemap_entry_t *const INEXACT_ENTRY = (jl_typemap_entry_t*)(uintptr_t)-1; -jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_value_t **args, size_t n, int8_t offs); -jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *mn, jl_value_t **args, size_t n); -STATIC_INLINE jl_typemap_entry_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_value_t **args, size_t n, int8_t offs) +jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_value_t **args, size_t n, int8_t offs, size_t world); +jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *mn, jl_value_t **args, size_t n, size_t world); +STATIC_INLINE jl_typemap_entry_t *jl_typemap_assoc_exact(union jl_typemap_t ml_or_cache, jl_value_t **args, size_t n, int8_t offs, size_t world) { // NOTE: This function is a huge performance hot spot!! if (jl_typeof(ml_or_cache.unknown) == (jl_value_t*)jl_typemap_entry_type) { - return jl_typemap_entry_assoc_exact(ml_or_cache.leaf, args, n); + return jl_typemap_entry_assoc_exact(ml_or_cache.leaf, args, n, world); } else if (jl_typeof(ml_or_cache.unknown) == (jl_value_t*)jl_typemap_level_type) { - return jl_typemap_level_assoc_exact(ml_or_cache.node, args, n, offs); + return jl_typemap_level_assoc_exact(ml_or_cache.node, args, n, offs, world); } return NULL; } diff --git a/src/julia_threads.h b/src/julia_threads.h index 1b5eb5ad11856..0b496d9ac20b9 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -68,6 +68,7 @@ typedef struct { #define JL_MAX_BT_SIZE 80000 typedef struct _jl_tls_states_t { struct _jl_gcframe_t *pgcstack; + size_t world_age; struct _jl_value_t *exception_in_transit; volatile size_t *safepoint; // Whether it is safe to execute GC at the same time. diff --git a/src/task.c b/src/task.c index da456112945a8..c89fbaca3983a 100644 --- a/src/task.c +++ b/src/task.c @@ -257,6 +257,7 @@ static void NOINLINE JL_NORETURN start_task(void) jl_sigint_safepoint(ptls); } JL_TIMING(ROOT); + ptls->world_age = jl_world_counter; res = jl_apply(&t->start, 1); } JL_CATCH { @@ -265,6 +266,7 @@ static void NOINLINE JL_NORETURN start_task(void) jl_gc_wb(t, res); } } + jl_get_ptls_states()->world_age = jl_world_counter; // TODO finish_task(t, res); gc_debug_critical_error(); abort(); @@ -306,14 +308,16 @@ static void ctx_switch(jl_ptls_t ptls, jl_task_t *t, jl_jmp_buf *where) if (!jl_setjmp(ptls->current_task->ctx, 0)) { // backtraces don't survive task switches, see e.g. issue #12485 ptls->bt_size = 0; -#ifdef COPY_STACKS jl_task_t *lastt = ptls->current_task; +#ifdef COPY_STACKS save_stack(ptls, lastt); #endif // set up global state for new task - ptls->current_task->gcstack = ptls->pgcstack; + lastt->gcstack = ptls->pgcstack; + lastt->world_age = ptls->world_age; ptls->pgcstack = t->gcstack; + ptls->world_age = t->world_age; #ifdef JULIA_ENABLE_THREADING // If the current task is not holding any locks, free the locks list // so that it can be GC'd without leaking memory diff --git a/src/toplevel.c b/src/toplevel.c index 700a288569be5..2f0b85f2b8aac 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -73,14 +73,17 @@ static jl_function_t *jl_module_get_initializer(jl_module_t *m) return (jl_function_t*)jl_get_global(m, jl_symbol("__init__")); } + void jl_module_run_initializer(jl_module_t *m) { - jl_ptls_t ptls = jl_get_ptls_states(); jl_function_t *f = jl_module_get_initializer(m); if (f == NULL) return; + size_t last_age = jl_get_ptls_states()->world_age; JL_TRY { + jl_get_ptls_states()->world_age = jl_world_counter; jl_apply(&f, 1); + jl_get_ptls_states()->world_age = last_age; } JL_CATCH { if (jl_initerror_type == NULL) { @@ -88,12 +91,11 @@ void jl_module_run_initializer(jl_module_t *m) } else { jl_rethrow_other(jl_new_struct(jl_initerror_type, m->name, - ptls->exception_in_transit)); + jl_exception_in_transit)); } } } - // load time init procedure: in build mode, only record order static void jl_module_load_time_initialize(jl_module_t *m) { @@ -174,25 +176,30 @@ jl_value_t *jl_eval_module_expr(jl_expr_t *ex) jl_value_t *defaultdefs = NULL, *form = NULL; JL_GC_PUSH3(&last_module, &defaultdefs, &form); + size_t last_age = ptls->world_age; jl_module_t *task_last_m = ptls->current_task->current_module; ptls->current_task->current_module = ptls->current_module = newm; + jl_module_t *prev_outermost = outermost; size_t stackidx = module_stack.len; if (outermost == NULL) outermost = newm; - if (std_imports) { - // add `eval` function - defaultdefs = jl_call_scm_on_ast("module-default-defs", (jl_value_t*)ex); - jl_toplevel_eval_flex(defaultdefs, 0, 1); - defaultdefs = NULL; - } - jl_array_t *exprs = ((jl_expr_t*)jl_exprarg(ex, 2))->args; JL_TRY { - for(int i=0; i < jl_array_len(exprs); i++) { + if (std_imports) { + // add `eval` function + defaultdefs = jl_call_scm_on_ast("module-default-defs", (jl_value_t*)ex); + ptls->world_age = jl_world_counter; + jl_toplevel_eval_flex(defaultdefs, 0, 1); + defaultdefs = NULL; + } + + for (int i = 0; i < jl_array_len(exprs); i++) { // process toplevel form + ptls->world_age = jl_world_counter; form = jl_expand(jl_array_ptr_ref(exprs, i)); + ptls->world_age = jl_world_counter; (void)jl_toplevel_eval_flex(form, 1, 1); } } @@ -204,6 +211,7 @@ jl_value_t *jl_eval_module_expr(jl_expr_t *ex) jl_rethrow(); } JL_GC_POP(); + ptls->world_age = last_age; ptls->current_module = last_module; ptls->current_task->current_module = task_last_m; outermost = prev_outermost; @@ -233,8 +241,8 @@ jl_value_t *jl_eval_module_expr(jl_expr_t *ex) if (outermost == NULL || ptls->current_module == jl_main_module) { JL_TRY { - size_t i, l=module_stack.len; - for(i = stackidx; i < l; i++) { + size_t i, l = module_stack.len; + for (i = stackidx; i < l; i++) { jl_module_load_time_initialize((jl_module_t*)module_stack.items[i]); } assert(module_stack.len == l); @@ -634,7 +642,8 @@ jl_value_t *jl_toplevel_eval_flex(jl_value_t *e, int fast, int expanded) if (ewc) { li = jl_new_thunk(thk); - jl_type_infer(li, 0); + size_t world = jl_get_ptls_states()->world_age; + jl_type_infer(&li, world, 0); jl_value_t *dummy_f_arg = NULL; result = jl_call_method_internal(li, &dummy_f_arg, 1); } @@ -791,7 +800,7 @@ static jl_datatype_t *first_arg_datatype(jl_value_t *a, int got_tuple1) } // get DataType of first tuple element, or NULL if cannot be determined -jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes) +JL_DLLEXPORT jl_datatype_t *jl_first_argument_datatype(jl_value_t *argtypes) { return first_arg_datatype(argtypes, 0); } diff --git a/src/typemap.c b/src/typemap.c index 55fbd9f981379..e4d911c1881a8 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -596,11 +596,14 @@ int sigs_eq(jl_value_t *a, jl_value_t *b, int useenv) there tends to be lots of variation there. The type of the 0th argument (the function) is always the same for most functions. */ -static jl_typemap_entry_t *jl_typemap_assoc_by_type_(jl_typemap_entry_t *ml, jl_tupletype_t *types, int8_t inexact, jl_svec_t **penv) +static jl_typemap_entry_t *jl_typemap_assoc_by_type_(jl_typemap_entry_t *ml, jl_tupletype_t *types, + int8_t inexact, jl_svec_t **penv, size_t world) { size_t n = jl_field_count(types); int typesisva = n == 0 ? 0 : jl_is_vararg_type(jl_tparam(types, n-1)); - while (ml != (void*)jl_nothing) { + for (; ml != (void*)jl_nothing; ml = ml->next) { + if (world < ml->min_world || world > ml->max_world) + continue; // ignore replaced methods size_t lensig = jl_field_count(ml->sig); if (lensig == n || (ml->va && lensig <= n+1)) { int resetenv = 0, ismatch = 1; @@ -681,19 +684,19 @@ static jl_typemap_entry_t *jl_typemap_assoc_by_type_(jl_typemap_entry_t *ml, jl_ if (resetenv) *penv = jl_emptysvec; } - ml = ml->next; } return NULL; } -static jl_typemap_entry_t *jl_typemap_lookup_by_type_(jl_typemap_entry_t *ml, jl_tupletype_t *types, int8_t useenv) +static jl_typemap_entry_t *jl_typemap_lookup_by_type_(jl_typemap_entry_t *ml, jl_tupletype_t *types, int8_t useenv, size_t world) { - while (ml != (void*)jl_nothing) { + for (; ml != (void*)jl_nothing; ml = ml->next) { + if (world < ml->min_world || world > ml->max_world) + continue; // TODO: more efficient if (sigs_eq((jl_value_t*)types, (jl_value_t*)ml->sig, useenv)) { return ml; } - ml = ml->next; } return NULL; } @@ -702,7 +705,7 @@ static jl_typemap_entry_t *jl_typemap_lookup_by_type_(jl_typemap_entry_t *ml, jl // this is the general entry point for looking up a type in the cache // (as a subtype, or with typeseq) jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_tupletype_t *types, jl_svec_t **penv, - int8_t subtype_inexact__sigseq_useenv, int8_t subtype, int8_t offs) + int8_t subtype_inexact__sigseq_useenv, int8_t subtype, int8_t offs, size_t world) { if (jl_typeof(ml_or_cache.unknown) == (jl_value_t*)jl_typemap_level_type) { jl_typemap_level_t *cache = ml_or_cache.node; @@ -727,7 +730,7 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_ // If there is a type at offs, look in the optimized caches if (!subtype) { if (ty && jl_is_any(ty)) - return jl_typemap_assoc_by_type(cache->any, types, penv, subtype_inexact__sigseq_useenv, subtype, offs+1); + return jl_typemap_assoc_by_type(cache->any, types, penv, subtype_inexact__sigseq_useenv, subtype, offs+1, world); if (isva) // in lookup mode, want to match Vararg exactly, not as a subtype ty = NULL; } @@ -738,7 +741,7 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_ union jl_typemap_t ml = mtcache_hash_lookup(&cache->targ, a0, 1, offs); if (ml.unknown != jl_nothing) { jl_typemap_entry_t *li = jl_typemap_assoc_by_type(ml, types, penv, - subtype_inexact__sigseq_useenv, subtype, offs+1); + subtype_inexact__sigseq_useenv, subtype, offs+1, world); if (li) return li; } } @@ -748,7 +751,7 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_ union jl_typemap_t ml = mtcache_hash_lookup(&cache->arg1, ty, 0, offs); if (ml.unknown != jl_nothing) { jl_typemap_entry_t *li = jl_typemap_assoc_by_type(ml, types, penv, - subtype_inexact__sigseq_useenv, subtype, offs+1); + subtype_inexact__sigseq_useenv, subtype, offs+1, world); if (li) return li; } } @@ -756,41 +759,43 @@ jl_typemap_entry_t *jl_typemap_assoc_by_type(union jl_typemap_t ml_or_cache, jl_ } // Always check the list (since offs doesn't always start at 0) if (subtype) { - jl_typemap_entry_t *li = jl_typemap_assoc_by_type_(cache->linear, types, subtype_inexact__sigseq_useenv, penv); + jl_typemap_entry_t *li = jl_typemap_assoc_by_type_(cache->linear, types, subtype_inexact__sigseq_useenv, penv, world); if (li) return li; - return jl_typemap_assoc_by_type(cache->any, types, penv, subtype_inexact__sigseq_useenv, subtype, offs+1); + return jl_typemap_assoc_by_type(cache->any, types, penv, subtype_inexact__sigseq_useenv, subtype, offs+1, world); } else { - return jl_typemap_lookup_by_type_(cache->linear, types, subtype_inexact__sigseq_useenv); + return jl_typemap_lookup_by_type_(cache->linear, types, subtype_inexact__sigseq_useenv, world); } } else { return subtype ? - jl_typemap_assoc_by_type_(ml_or_cache.leaf, types, subtype_inexact__sigseq_useenv, penv) : - jl_typemap_lookup_by_type_(ml_or_cache.leaf, types, subtype_inexact__sigseq_useenv); + jl_typemap_assoc_by_type_(ml_or_cache.leaf, types, subtype_inexact__sigseq_useenv, penv, world) : + jl_typemap_lookup_by_type_(ml_or_cache.leaf, types, subtype_inexact__sigseq_useenv, world); } } -jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_value_t **args, size_t n) +jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_value_t **args, size_t n, size_t world) { // some manually-unrolled common special cases while (ml->simplesig == (void*)jl_nothing && ml->guardsigs == jl_emptysvec && ml->isleafsig) { // use a tight loop for a long as possible - if (n == jl_field_count(ml->sig) && jl_typeof(args[0]) == jl_tparam(ml->sig, 0)) { - if (n == 1) - return ml; - if (n == 2) { - if (jl_typeof(args[1]) == jl_tparam(ml->sig, 1)) - return ml; - } - else if (n == 3) { - if (jl_typeof(args[1]) == jl_tparam(ml->sig, 1) && - jl_typeof(args[2]) == jl_tparam(ml->sig, 2)) - return ml; - } - else { - if (sig_match_leaf(args, jl_svec_data(ml->sig->parameters), n)) + if (world >= ml->min_world && world <= ml->max_world) { + if (n == jl_field_count(ml->sig) && jl_typeof(args[0]) == jl_tparam(ml->sig, 0)) { + if (n == 1) return ml; + if (n == 2) { + if (jl_typeof(args[1]) == jl_tparam(ml->sig, 1)) + return ml; + } + else if (n == 3) { + if (jl_typeof(args[1]) == jl_tparam(ml->sig, 1) && + jl_typeof(args[2]) == jl_tparam(ml->sig, 2)) + return ml; + } + else { + if (sig_match_leaf(args, jl_svec_data(ml->sig->parameters), n)) + return ml; + } } } ml = ml->next; @@ -798,7 +803,9 @@ jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_valu return NULL; } - while (ml != (void*)jl_nothing) { + for (; ml != (void*)jl_nothing; ml = ml->next) { + if (world < ml->min_world || world > ml->max_world) + continue; // ignore replaced methods size_t lensig = jl_field_count(ml->sig); if (lensig == n || (ml->va && lensig <= n+1)) { if (ml->simplesig != (void*)jl_nothing) { @@ -806,24 +813,24 @@ jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_valu int isva = lensimplesig > 0 && jl_is_vararg_type(jl_tparam(ml->simplesig, lensimplesig - 1)); if (lensig == n || (isva && lensimplesig <= n + 1)) { if (!sig_match_simple(args, n, jl_svec_data(ml->simplesig->parameters), isva, lensimplesig)) - goto nomatch; + continue; } else { - goto nomatch; + continue; } } if (ml->isleafsig) { if (!sig_match_leaf(args, jl_svec_data(ml->sig->parameters), n)) - goto nomatch; + continue; } else if (ml->issimplesig) { if (!sig_match_simple(args, n, jl_svec_data(ml->sig->parameters), ml->va, lensig)) - goto nomatch; + continue; } else { if (!jl_tuple_subtype(args, n, ml->sig, 1)) - goto nomatch; + continue; } size_t i, l; @@ -838,14 +845,14 @@ jl_typemap_entry_t *jl_typemap_entry_assoc_exact(jl_typemap_entry_t *ml, jl_valu } } return ml; - } nomatch: - ml = ml->next; + continue; + } } return NULL; } -jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_value_t **args, size_t n, int8_t offs) +jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_value_t **args, size_t n, int8_t offs, size_t world) { if (n > offs) { jl_value_t *a1 = args[offs]; @@ -853,21 +860,21 @@ jl_typemap_entry_t *jl_typemap_level_assoc_exact(jl_typemap_level_t *cache, jl_v assert(jl_is_datatype(ty)); if (ty == (jl_value_t*)jl_datatype_type && cache->targ.values != (void*)jl_nothing) { union jl_typemap_t ml_or_cache = mtcache_hash_lookup(&cache->targ, a1, 1, offs); - jl_typemap_entry_t *ml = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1); + jl_typemap_entry_t *ml = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1, world); if (ml) return ml; } if (cache->arg1.values != (void*)jl_nothing) { union jl_typemap_t ml_or_cache = mtcache_hash_lookup(&cache->arg1, ty, 0, offs); - jl_typemap_entry_t *ml = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1); + jl_typemap_entry_t *ml = jl_typemap_assoc_exact(ml_or_cache, args, n, offs+1, world); if (ml) return ml; } } if (cache->linear != (jl_typemap_entry_t*)jl_nothing) { - jl_typemap_entry_t *ml = jl_typemap_entry_assoc_exact(cache->linear, args, n); + jl_typemap_entry_t *ml = jl_typemap_entry_assoc_exact(cache->linear, args, n, world); if (ml) return ml; } if (cache->any.unknown != jl_nothing) - return jl_typemap_assoc_exact(cache->any, args, n, offs+1); + return jl_typemap_assoc_exact(cache->any, args, n, offs+1, world); return NULL; } @@ -1008,39 +1015,28 @@ jl_typemap_entry_t *jl_typemap_insert(union jl_typemap_t *cache, jl_value_t *par jl_tupletype_t *simpletype, jl_svec_t *guardsigs, jl_value_t *newvalue, int8_t offs, const struct jl_typemap_info *tparams, + size_t min_world, size_t max_world, jl_value_t **overwritten) { jl_ptls_t ptls = jl_get_ptls_states(); + assert(min_world > 0 && max_world > 0); assert(jl_is_tuple_type(type)); if (!simpletype) { simpletype = (jl_tupletype_t*)jl_nothing; } if ((jl_value_t*)simpletype == jl_nothing) { - jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(*cache, type, NULL, 1, 0, offs); + jl_typemap_entry_t *ml = jl_typemap_assoc_by_type(*cache, type, NULL, 1, 0, offs, min_world); if (ml && ml->simplesig == (void*)jl_nothing) { + if (newvalue == ml->func.value) // no change. TODO: involve world in computation! + return ml; if (overwritten != NULL) *overwritten = ml->func.value; if (newvalue == NULL) // don't overwrite with guard entries return ml; - // sigatomic begin - ml->sig = type; - jl_gc_wb(ml, ml->sig); - ml->simplesig = simpletype; - jl_gc_wb(ml, ml->simplesig); - ml->tvars = tvars; - jl_gc_wb(ml, ml->tvars); - ml->va = jl_is_va_tuple(type); - // TODO: `l->func` or `l->func->roots` might need to be rooted - ml->func.value = newvalue; - if (newvalue) - jl_gc_wb(ml, newvalue); - // sigatomic end - return ml; + ml->max_world = min_world - 1; } } - if (overwritten != NULL) - *overwritten = NULL; jl_typemap_entry_t *newrec = (jl_typemap_entry_t*)jl_gc_alloc(ptls, sizeof(jl_typemap_entry_t), @@ -1051,6 +1047,8 @@ jl_typemap_entry_t *jl_typemap_insert(union jl_typemap_t *cache, jl_value_t *par newrec->func.value = newvalue; newrec->guardsigs = guardsigs; newrec->next = (jl_typemap_entry_t*)jl_nothing; + newrec->min_world = min_world; + newrec->max_world = max_world; // compute the complexity of this type signature newrec->va = jl_is_va_tuple(type); newrec->issimplesig = (tvars == jl_emptysvec); // a TypeVar environment needs an complex matching test diff --git a/test/choosetests.jl b/test/choosetests.jl index 46310500bd2b4..98634c0c87073 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -15,7 +15,8 @@ Upon return, `tests` is a vector of fully-expanded test names, and """ -> function choosetests(choices = []) testnames = [ - "linalg", "subarray", "core", "inference", "keywordargs", "numbers", + "linalg", "subarray", "core", "inference", "worlds", + "keywordargs", "numbers", "printf", "char", "strings", "triplequote", "unicode", "dates", "dict", "hashing", "iobuffer", "staged", "offsetarray", "arrayops", "tuple", "reduce", "reducedim", "random", "abstractarray", diff --git a/test/compile.jl b/test/compile.jl index 65d4669e9687a..5057190db4e33 100644 --- a/test/compile.jl +++ b/test/compile.jl @@ -121,8 +121,8 @@ try let some_method = @which Base.include("string") # global const some_method // FIXME: support for serializing a direct reference to an external Method not implemented global const some_linfo = - ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), - some_method, Tuple{typeof(Base.include), String}, Core.svec()) + ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any, UInt), + some_method, Tuple{typeof(Base.include), String}, Core.svec(), typemax(UInt)) end end """) @@ -146,6 +146,14 @@ try wait(t) let Foo = getfield(Main, Foo_module) + @test_throws MethodError Foo.foo(17) # world shouldn't be visible yet + end + @eval let Foo_module = $(QuoteNode(Foo_module)), # use @eval to see the results of loading the compile + FooBase_module = $(QuoteNode(FooBase_module)), + Foo = getfield(Main, Foo_module), + dir = $(QuoteNode(dir)), + cachefile = $(QuoteNode(cachefile)), + Foo_file = $(QuoteNode(Foo_file)) @test Foo.foo(17) == 18 @test Foo.Bar.bar(17) == 19 @@ -187,8 +195,8 @@ try 0:25) some_method = @which Base.include("string") some_linfo = - ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any), - some_method, Tuple{typeof(Base.include), String}, Core.svec()) + ccall(:jl_specializations_get_linfo, Ref{MethodInstance}, (Any, Any, Any, UInt), + some_method, Tuple{typeof(Base.include), String}, Core.svec(), typemax(UInt)) @test Foo.some_linfo::Core.MethodInstance === some_linfo PV = Foo.Value18343{Nullable}.types[1] diff --git a/test/core.jl b/test/core.jl index c0c076b516aa5..d0a43d40effbd 100644 --- a/test/core.jl +++ b/test/core.jl @@ -3367,7 +3367,7 @@ typealias PossiblyInvalidUnion{T} Union{T,Int} # issue #13007 call13007{T,N}(::Type{Array{T,N}}) = 0 call13007(::Type{Array}) = 1 -@test length(Base._methods(call13007, Tuple{Type{TypeVar(:_,Array)}}, 4)) == 2 +@test length(Base._methods(call13007, Tuple{Type{TypeVar(:_,Array)}}, 4, typemax(UInt))) == 2 # detecting cycles during type intersection, e.g. #1631 cycle_in_solve_tvar_constraints{S}(::Type{Nullable{S}}, x::S) = 0 diff --git a/test/loading.jl b/test/loading.jl index 01865b0379b65..7e7f707d1b1f9 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -6,7 +6,8 @@ using Base.Test include("test_sourcepath.jl") thefname = "the fname!//\\&\1*" -@test include_string("include_string_test() = @__FILE__", thefname)() == Base.source_path() +include_string_test_func = include_string("include_string_test() = @__FILE__", thefname) +@test include_string_test_func() == Base.source_path() @test include_string("Base.source_path()", thefname) == Base.source_path() @test basename(@__FILE__) == "loading.jl" @test isabspath(@__FILE__) diff --git a/test/reflection.jl b/test/reflection.jl index 1cf82befceec3..59b572d399a9f 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -339,10 +339,15 @@ end # test jl_get_llvm_fptr. We test functions both in and definitely not in the system image definitely_not_in_sysimg() = nothing -for (f,t) in ((definitely_not_in_sysimg,Tuple{}), - (Base.throw_boundserror,Tuple{UnitRange{Int64},Int64})) - t = Base.tt_cons(Core.Typeof(f), Base.to_tuple_type(t)) - llvmf = ccall(:jl_get_llvmf, Ptr{Void}, (Any, Bool, Bool), t, false, true) +for (f, t) in Any[(definitely_not_in_sysimg, Tuple{}), + (Base.:+, Tuple{Int, Int})] + meth = which(f, t) + tt = Tuple{typeof(f), t.parameters...} + env = (ccall(:jl_match_method, Any, (Any, Any, Any), tt, meth.sig, meth.tvars))[2] + world = typemax(UInt) + linfo = ccall(:jl_specializations_get_linfo, Ref{Core.MethodInstance}, (Any, Any, Any, UInt), meth, tt, env, world) + params = Base.CodegenParams() + llvmf = ccall(:jl_get_llvmf_decl, Ptr{Void}, (Any, UInt, Bool, Base.CodegenParams), linfo::Core.MethodInstance, world, true, params) @test llvmf != C_NULL @test ccall(:jl_get_llvm_fptr, Ptr{Void}, (Ptr{Void},), llvmf) != C_NULL end @@ -586,6 +591,7 @@ end # PR #18888: code_typed shouldn't cache if not optimizing let + world = typemax(UInt) f18888() = return nothing m = first(methods(f18888, Tuple{})) @test m.specializations === nothing @@ -593,11 +599,11 @@ let code_typed(f18888, Tuple{}; optimize=false) @test m.specializations !== nothing # uncached, but creates the specializations entry - code = Core.Inference.code_for_method(m, Tuple{ft}, Core.svec(), true) + code = Core.Inference.code_for_method(m, Tuple{ft}, Core.svec(), world, true) @test !isdefined(code, :inferred) code_typed(f18888, Tuple{}; optimize=true) - code = Core.Inference.code_for_method(m, Tuple{ft}, Core.svec(), true) + code = Core.Inference.code_for_method(m, Tuple{ft}, Core.svec(), world, true) @test isdefined(code, :inferred) end diff --git a/test/runtests.jl b/test/runtests.jl index 5172d85719437..a1a7bd28c42df 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -108,7 +108,7 @@ cd(dirname(@__FILE__)) do n > 1 && print("\tFrom worker 1:\t") local resp try - resp = runtests(t) + resp = eval(Expr(:call, () -> runtests(t))) # runtests is defined by the include above catch e resp = [e] end diff --git a/test/staged.jl b/test/staged.jl index 3a763891f9e1c..3be262f9779f8 100644 --- a/test/staged.jl +++ b/test/staged.jl @@ -154,10 +154,15 @@ end # @generated functions including inner functions @generated function _g_f_with_inner(x) - :(y->y) + return :(y -> y) end @test_throws ErrorException _g_f_with_inner(1) +@generated function _g_f_with_inner2(x) + return y -> y +end +@test _g_f_with_inner2(1)(2) == 2 + # @generated functions errors global gf_err_ref = Ref{Int}() diff --git a/test/worlds.jl b/test/worlds.jl new file mode 100644 index 0000000000000..99d4228e94f41 --- /dev/null +++ b/test/worlds.jl @@ -0,0 +1,152 @@ +# This file is a part of Julia. License is MIT: http://julialang.org/license + +# tests for accurate updating of method tables + +tls_world_age() = ccall(:jl_get_tls_world_age, UInt, ()) +world_counter() = ccall(:jl_get_world_counter, UInt, ()) +@test typemax(UInt) > world_counter() == tls_world_age() > 0 + +# test simple method replacement +begin + g265a() = f265a(0) + f265a(x::Any) = 1 + @test g265a() == 1 + @test Base.return_types(g265a, ()) == Any[Int] + @test Core.Inference.return_type(g265a, ()) == Int + + f265a(x::Any) = 2.0 + @test g265a() == 2.0 + + @test Base.return_types(g265a, ()) == Any[Float64] + @test Core.Inference.return_type(g265a, ()) == Float64 +end + +# test signature widening +begin + f265b(x::Int) = 1 + let ty = Any[1, 2.0e0] + global g265b(i::Int) = f265b(ty[i]) + end + @test g265b(1) == 1 + @test Base.return_types(g265b, (Int,)) == Any[Int] + @test Core.Inference.return_type(g265b, (Int,)) == Int + + f265b(x::Any) = 2.0 + @test g265b(1) == 1 + @test g265b(2) == 2.0 + @test Base.return_types(g265b, (Int,)) == Any[Union{Int, Float64}] + @test Core.Inference.return_type(g265b, (Int,)) == Union{Int, Float64} +end + +# test signature narrowing +begin + g265c() = f265c(0) + f265c(x::Any) = 1 + @test g265c() == 1 + @test Base.return_types(g265c, ()) == Any[Int] + @test Core.Inference.return_type(g265c, ()) == Int + + f265c(x::Int) = 2.0 + @test g265c() == 2.0 + + @test Base.return_types(g265c, ()) == Any[Float64] + @test Core.Inference.return_type(g265c, ()) == Float64 +end + +# test constructor narrowing +type A265{T} + field1::T +end +A265_() = A265(1) +@test (A265_()::A265{Int}).field1 === 1 +A265(fld::Int) = A265(Float64(fld)) +@test (A265_()::A265{Float64}).field1 === 1.0e0 + +# test constructor widening +type B265{T} + field1::T + # dummy arg is present to prevent (::Type{T}){T}(arg) from matching the test calls + B265(field1::Any, dummy::Void) = new(field1) # prevent generation of outer ctor +end + # define some constructors +B265(x::Int, dummy::Void) = B265{Int}(x, dummy) +let ty = Any[1, 2.0e0, 3.0f0] + global B265_(i::Int) = B265(ty[i], nothing) +end + # test for correct answers +@test (B265_(1)::B265{Int}).field1 === 1 +@test_throws MethodError B265_(2) +@test_throws MethodError B265_(3) +@test Base.return_types(B265_, (Int,)) == Any[B265{Int}] +@test Core.Inference.return_type(B265_, (Int,)) == B265{Int} + + # add new constructors +B265(x::Float64, dummy::Void) = B265{Float64}(x, dummy) +B265(x::Any, dummy::Void) = B265{UInt8}(x, dummy) + + # make sure answers are updated +@test (B265_(1)::B265{Int}).field1 === 1 +@test (B265_(2)::B265{Float64}).field1 === 2.0e0 +@test (B265_(3)::B265{UInt8}).field1 === 0x03 + +@test Base.return_types(B265_, (Int,)) == Any[Union{B265{Float64}, B265{Int}, B265{UInt8}}] +@test Core.Inference.return_type(B265_, (Int,)) == Union{B265{Float64}, B265{Int}, B265{UInt8}} + + +# test oldworld call / inference +g265() = [f265(x) for x in 1:3.] +wc265 = world_counter() +f265(::Any) = 1.0 +@test wc265 + 1 == world_counter() +t265 = @async begin + local ret = nothing + while true + (f, args) = produce(ret) + ret = f(args...) + end +end +@test consume(t265) === nothing +wc265 = world_counter() +@test consume(t265, world_counter, ()) == wc265 +@test consume(t265, tls_world_age, ()) == wc265 +f265(::Int) = 1 +@test consume(t265, world_counter, ()) == wc265 + 1 == world_counter() == tls_world_age() +@test consume(t265, tls_world_age, ()) == wc265 + +@test g265() == Int[1, 1, 1] +@test Core.Inference.return_type(f265, (Any,)) == Union{Float64, Int} +@test Core.Inference.return_type(f265, (Int,)) == Int +@test Core.Inference.return_type(f265, (Float64,)) == Float64 + +@test consume(t265, g265, ()) == Float64[1.0, 1.0, 1.0] +@test consume(t265, Core.Inference.return_type, (f265, (Any,))) == Float64 +@test consume(t265, Core.Inference.return_type, (f265, (Int,))) == Float64 +@test consume(t265, Core.Inference.return_type, (f265, (Float64,))) == Float64 +@test consume(t265, Core.Inference.return_type, (f265, (Float64,))) == Float64 + + +# test that reflection ignores worlds +@test Base.return_types(f265, (Any,)) == Any[Int, Float64] +@test consume(t265, Base.return_types, (f265, (Any,))) == Any[Int, Float64] + + +# test for method errors +h265() = true +loc_h265 = "$(Base.source_path()):$(@__LINE__ - 1)" +@test h265() +@test_throws MethodError consume(t265, h265, ()) +@test_throws MethodError wait(t265) +@test istaskdone(t265) +let ex = t265.exception + @test ex.f == h265 + @test ex.args == () + @test ex.world == wc265 + str = sprint(showerror, ex) + wc = world_counter() + cmp = """ + MethodError: no method matching h265() + The applicable method may be too new: running in world age $wc265, while current world is $wc.""" + @test startswith(str, cmp) + cmp = "\n h265() at $loc_h265 (method too new to be called from this world context.)" + @test contains(str, cmp) +end diff --git a/ui/repl.c b/ui/repl.c index 569d294ffaaa3..bbd0cb02b6bb9 100644 --- a/ui/repl.c +++ b/ui/repl.c @@ -121,7 +121,10 @@ static NOINLINE int true_main(int argc, char *argv[]) if (start_client) { JL_TRY { + size_t last_age = jl_get_ptls_states()->world_age; + jl_get_ptls_states()->world_age = jl_get_world_counter(); jl_apply(&start_client, 1); + jl_get_ptls_states()->world_age = last_age; } JL_CATCH { jl_no_exc_handler(jl_exception_in_transit); From 18b9bf97a8ef92372e7b09659f0f01900a6931a5 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 22 Sep 2016 16:45:16 -0400 Subject: [PATCH 2/8] add some debugging for world-counter --- base/inference.jl | 1 + src/alloc.c | 12 ++++++++++++ src/gc.c | 3 +++ src/options.h | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/base/inference.jl b/base/inference.jl index 6f6ad506edadb..3d22339c54396 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -1735,6 +1735,7 @@ end # build (and start inferring) the inference frame for the linfo function typeinf_frame(linfo::MethodInstance, caller, optimize::Bool, cached::Bool, params::InferenceParams) + # println(params.world, ' ', linfo) if cached && linfo.inInference # inference on this signature may be in progress, # find the corresponding frame in the active list diff --git a/src/alloc.c b/src/alloc.c index acec1df4b79fc..5731f4a72db79 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -655,6 +655,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(void) return m; } +jl_array_t *jl_all_methods; jl_method_t *jl_new_method(jl_code_info_t *definition, jl_sym_t *name, jl_tupletype_t *sig, @@ -692,6 +693,17 @@ jl_method_t *jl_new_method(jl_code_info_t *definition, m->unspecialized->inferred = (jl_value_t*)m->source; m->source = NULL; } + +#ifdef RECORD_METHOD_ORDER + if (jl_all_methods == NULL) + jl_all_methods = jl_alloc_vec_any(0); +#endif + if (jl_all_methods != NULL) { + while (jl_array_len(jl_all_methods) < jl_world_counter) + jl_array_ptr_1d_push(jl_all_methods, NULL); + jl_array_ptr_1d_push(jl_all_methods, (jl_value_t*)m); + } + JL_GC_POP(); return m; } diff --git a/src/gc.c b/src/gc.c index d32cd98dbd8ad..83a22e9394466 100644 --- a/src/gc.c +++ b/src/gc.c @@ -1543,6 +1543,7 @@ void visit_mark_stack(jl_ptls_t ptls) extern jl_array_t *jl_module_init_order; extern jl_typemap_entry_t *call_cache[N_CALL_CACHE]; +extern jl_array_t *jl_all_methods; // mark the initial root set void pre_mark(jl_ptls_t ptls) @@ -1575,6 +1576,8 @@ void pre_mark(jl_ptls_t ptls) for (i = 0; i < N_CALL_CACHE; i++) if (call_cache[i]) gc_push_root(ptls, call_cache[i], 0); + if (jl_all_methods != NULL) + gc_push_root(ptls, jl_all_methods, 0); jl_mark_box_caches(ptls); //gc_push_root(ptls, jl_unprotect_stack_func, 0); diff --git a/src/options.h b/src/options.h index c48f729b6865e..93d1f8fb76939 100644 --- a/src/options.h +++ b/src/options.h @@ -27,6 +27,11 @@ // delete julia IR for non-inlineable functions after they're codegen'd #define JL_DELETE_NON_INLINEABLE 1 +// fill in the jl_all_methods in world-counter order +// so that it is possible to map (in a debugger) from +// an inferred world validity range back to the offending definition +// #define RECORD_METHOD_ORDER + // GC options ----------------------------------------------------------------- // debugging options From 5db1b4e4e6660c2f7e11f49689efd8b7de05291d Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 23 Sep 2016 13:49:23 -0400 Subject: [PATCH 3/8] run generators in a correct & useful world --- src/alloc.c | 5 ++++- test/worlds.jl | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/alloc.c b/src/alloc.c index 5731f4a72db79..c3b5c376accf1 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -494,11 +494,14 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) int last_in = ptls->in_pure_callback; jl_module_t *last_m = ptls->current_module; jl_module_t *task_last_m = ptls->current_task->current_module; + size_t last_age = jl_get_ptls_states()->world_age; assert(jl_svec_len(linfo->def->sparam_syms) == jl_svec_len(sparam_vals)); JL_TRY { ptls->in_pure_callback = 1; // need to eval macros in the right module ptls->current_task->current_module = ptls->current_module = linfo->def->module; + // and the right world + ptls->world_age = generator->def->min_world; ex = jl_exprn(lambda_sym, 2); @@ -519,7 +522,6 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) // invoke code generator assert(jl_nparams(tt) == jl_array_len(argnames) || (linfo->def->isva && (jl_nparams(tt) >= jl_array_len(argnames) - 1))); - // TODO: set world to that of the generator while calling func jl_array_ptr_set(body->args, 1, jl_call_staged(sparam_vals, generator, jl_svec_data(tt->parameters), jl_nparams(tt))); @@ -549,6 +551,7 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) jl_lineno = last_lineno; ptls->current_module = last_m; ptls->current_task->current_module = task_last_m; + ptls->world_age = last_age; } JL_CATCH { ptls->in_pure_callback = last_in; diff --git a/test/worlds.jl b/test/worlds.jl index 99d4228e94f41..cd28b0039f429 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -150,3 +150,16 @@ let ex = t265.exception cmp = "\n h265() at $loc_h265 (method too new to be called from this world context.)" @test contains(str, cmp) end + +# test for generated function correctness +# and min/max world computation validity of cache_method +f_gen265(x) = 1 +@generated g_gen265(x) = f_gen265(x) +@generated h_gen265(x) = :(f_gen265(x)) +f_gen265(x::Int) = 2 +f_gen265(x::Type{Int}) = 3 +@generated g_gen265b(x) = f_gen265(x) +@test h_gen265(0) == 2 +@test g_gen265(0) == 1 +@test f_gen265(Int) == 3 +@test g_gen265b(0) == 3 From aa9ba8d5a5850edf5a015b5ad90a7397456c39d5 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 4 Oct 2016 14:14:58 -0400 Subject: [PATCH 4/8] move convert overwrite back to the beginning of sysimg the world counter now handles making sure that type inference isn't affected by this --- base/coreimg.jl | 14 ++++++-------- base/precompile.jl | 3 +++ base/sysimg.jl | 14 +++++++------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/base/coreimg.jl b/base/coreimg.jl index 6e831935e4b40..2e2aa989048a6 100644 --- a/base/coreimg.jl +++ b/base/coreimg.jl @@ -6,14 +6,16 @@ import Core: print, println, show, write, unsafe_write, STDOUT, STDERR ccall(:jl_set_istopmod, Void, (Bool,), false) -eval(x) = Core.eval(Inference,x) -eval(m,x) = Core.eval(m,x) +eval(x) = Core.eval(Inference, x) +eval(m, x) = Core.eval(m, x) -include = Core.include +const include = Core.include +# conditional to allow redefining Core.Inference after base exists +isdefined(Main, :Base) || ((::Type{T}){T}(arg) = convert(T, arg)::T) ## Load essential files and libraries -include("ctypes.jl") include("essentials.jl") +include("ctypes.jl") include("generator.jl") include("reflection.jl") include("options.jl") @@ -33,10 +35,6 @@ include("operators.jl") include("pointer.jl") const checked_add = + const checked_sub = - -if !isdefined(Main, :Base) - # conditional to allow redefining Core.Inference after base exists - (::Type{T}){T}(arg) = convert(T, arg)::T -end # core array operations include("array.jl") diff --git a/base/precompile.jl b/base/precompile.jl index ee70a0744a4e0..c8ce400142c17 100644 --- a/base/precompile.jl +++ b/base/precompile.jl @@ -454,6 +454,9 @@ precompile(Base.string, (String, String, Char)) precompile(Base.string, (String, String, Int)) precompile(Base.vect, (Base.LineEdit.Prompt, String)) +# Speed up type inference in the post-Base world redefinition of convert +isdefined(Core, :Inference) && Base.code_typed(Base.code_typed) + # Speeding up addprocs for LocalManager precompile(Base.start_worker, ()) precompile(Base.start_worker, (Base.TTY,)) diff --git a/base/sysimg.jl b/base/sysimg.jl index af90f85eceeae..a25f2ac5895e9 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -19,8 +19,12 @@ INCLUDE_STATE = 1 # include = Core.include include("coreio.jl") -eval(x) = Core.eval(Base,x) -eval(m,x) = Core.eval(m,x) +eval(x) = Core.eval(Base, x) +eval(m, x) = Core.eval(m, x) +(::Type{T}){T}(arg) = convert(T, arg)::T +(::Type{VecElement{T}}){T}(arg) = VecElement{T}(convert(T, arg)) +convert{T<:VecElement}(::Type{T}, arg) = T(arg) +convert{T<:VecElement}(::Type{T}, arg::T) = arg # init core docsystem import Core: @doc, @__doc__, @doc_str @@ -42,8 +46,8 @@ if false end ## Load essential files and libraries -include("ctypes.jl") include("essentials.jl") +include("ctypes.jl") include("base.jl") include("generator.jl") include("reflection.jl") @@ -63,10 +67,6 @@ include("int.jl") include("operators.jl") include("pointer.jl") include("refpointer.jl") -(::Type{T}){T}(arg) = convert(T, arg)::T -(::Type{VecElement{T}}){T}(arg) = VecElement{T}(convert(T, arg)) -convert{T<:VecElement}(::Type{T}, arg) = T(arg) -convert{T<:VecElement}(::Type{T}, arg::T) = arg include("checked.jl") importall .Checked From 6f50c60fb0d7c2d81e322a2143d7467a6b79887c Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 26 Oct 2016 14:18:14 -0400 Subject: [PATCH 5/8] mitigate the performance impact of gen_broadcast_function_sparse being a fake generated function the previous version allocates slightly less, but is much slower since it has to pass through the frontend expansion pass --- base/sparse/sparsematrix.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/base/sparse/sparsematrix.jl b/base/sparse/sparsematrix.jl index 5318314fe7e12..e2da90d672359 100644 --- a/base/sparse/sparsematrix.jl +++ b/base/sparse/sparsematrix.jl @@ -2253,7 +2253,9 @@ for (Bsig, A1sig, A2sig, gbb, funcname) in global $funcname function $funcname(f::Function, B::$Bsig, A1::$A1sig, A2::$A2sig) func = @get! cache f gen_broadcast_function_sparse($gbb, f, ($A1sig) <: SparseMatrixCSC) - eval(Expr(:call, () -> func(B, A1, A2))) # need eval because func was just created by gen_broadcast_function_sparse + # need eval because func was just created by gen_broadcast_function_sparse + # TODO: convert this to a generated function + eval(current_module(), Expr(:body, Expr(:return, Expr(:call, QuoteNode(func), QuoteNode(B), QuoteNode(A1), QuoteNode(A2))))) return B end end # let broadcast_cache From 411beffe4670fa7da65f96df8a9f5acfb5e80567 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 17 Dec 2016 16:25:28 -0500 Subject: [PATCH 6/8] run cfunctions in the correct world The only sensible world to run in is to capture the exact world age when `cfunction` is called. This is the only way that it is possible to run inference on it, and thus the only way to be fast. --- src/codegen.cpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index a7464aae6c2fb..48a36c7b77ee3 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3635,8 +3635,16 @@ static void allocate_gc_frame(BasicBlock *b0, jl_codectx_t *ctx) PointerType::get(T_psize, 0)); } +static void emit_last_age_field(jl_codectx_t *ctx) +{ + ctx->world_age_field = builder.CreateGEP( + builder.CreateBitCast(ctx->ptlsStates, T_psize), + ConstantInt::get(T_size, offsetof(jl_tls_states_t, world_age) / sizeof(size_t))); +} + static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_tupletype_t *argt, - jl_typemap_entry_t *sf, jl_value_t *declrt, jl_tupletype_t *sigt) + jl_typemap_entry_t *sf, jl_value_t *declrt, jl_tupletype_t *sigt, + size_t world) { // Generate a c-callable wrapper bool toboxed; @@ -3663,7 +3671,6 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t const char *name = "cfunction"; // try to look up this function for direct invoking - size_t world = jl_world_counter/*FIXME*/; jl_method_instance_t *lam = jl_get_specialization1((jl_tupletype_t*)sigt, world); jl_value_t *astrt = (jl_value_t*)jl_any_type; // infer it first, if necessary @@ -3721,6 +3728,14 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t ctx.spvals_ptr = NULL; ctx.params = &jl_default_cgparams; allocate_gc_frame(b0, &ctx); + emit_last_age_field(&ctx); + Value *dummy_world = builder.CreateAlloca(T_size); + Value *have_tls = builder.CreateICmpNE(ctx.ptlsStates, Constant::getNullValue(ctx.ptlsStates->getType())); + // TODO: in the future, try to initialize a full TLS context here + // for now, just use a dummy field to avoid a branch in this function + ctx.world_age_field = builder.CreateSelect(have_tls, ctx.world_age_field, dummy_world); + Value *last_age = tbaa_decorate(tbaa_gcframe, builder.CreateLoad(ctx.world_age_field)); + builder.CreateStore(ConstantInt::get(T_size, world), ctx.world_age_field); // Save the Function object reference sf->func.value = jl_box_voidpointer((void*)cw_proto); @@ -3948,6 +3963,7 @@ static Function *gen_cfun_wrapper(jl_function_t *ff, jl_value_t *jlrettype, jl_t sret = true; } + builder.CreateStore(last_age, ctx.world_age_field); if (sret) builder.CreateRetVoid(); else @@ -4019,7 +4035,7 @@ static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_t cfunc_sig = (jl_value_t*)jl_apply_tuple_type((jl_svec_t*)cfunc_sig); // check the cache - size_t world = 1; + size_t world = jl_world_counter; if (jl_cfunction_list.unknown != jl_nothing) { jl_typemap_entry_t *sf = jl_typemap_assoc_by_type(jl_cfunction_list, (jl_tupletype_t*)cfunc_sig, NULL, 1, 0, /*offs*/0, world); if (sf) { @@ -4031,7 +4047,7 @@ static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_t } } jl_typemap_entry_t *sf = jl_typemap_insert(&jl_cfunction_list, (jl_value_t*)jl_cfunction_list.unknown, (jl_tupletype_t*)cfunc_sig, - jl_emptysvec, NULL, jl_emptysvec, NULL, /*offs*/0, &cfunction_cache, 1, ~(size_t)0, NULL); + jl_emptysvec, NULL, jl_emptysvec, NULL, /*offs*/0, &cfunction_cache, world, world, NULL); // Backup the info for the nested compile JL_LOCK(&codegen_lock); @@ -4039,7 +4055,7 @@ static Function *jl_cfunction_object(jl_function_t *ff, jl_value_t *declrt, jl_t DebugLoc olddl = builder.getCurrentDebugLocation(); bool last_n_c = nested_compile; nested_compile = true; - Function *f = gen_cfun_wrapper(ff, crt, (jl_tupletype_t*)argt, sf, declrt, (jl_tupletype_t*)sigt); + Function *f = gen_cfun_wrapper(ff, crt, (jl_tupletype_t*)argt, sf, declrt, (jl_tupletype_t*)sigt, world); // Restore the previous compile context builder.restoreIP(old); builder.SetCurrentDebugLocation(olddl); @@ -4584,12 +4600,9 @@ static std::unique_ptr emit_function( // step 7. set up GC frame allocate_gc_frame(b0, &ctx); - Value *last_age = NULL; if (toplevel) { - ctx.world_age_field = builder.CreateGEP( - builder.CreateBitCast(ctx.ptlsStates, T_psize), - ConstantInt::get(T_size, offsetof(jl_tls_states_t, world_age) / sizeof(size_t))); + emit_last_age_field(&ctx); last_age = tbaa_decorate(tbaa_gcframe, builder.CreateLoad(ctx.world_age_field)); } From b536c003023aaa4cf9ac9b4885e0f561098d5bfe Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 18 Dec 2016 21:58:16 -0500 Subject: [PATCH 7/8] add NEWs entry for #265 --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index b964900fa896e..63d22b8be96e4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,6 +22,9 @@ Language changes for `.*` etcetera. This also means that "dot operations" automatically fuse into a single loop, along with other dot calls `f.(x)`. ([#17623]) + * Newly defined methods are no longer callable from the same dynamic runtime + scope they were defined in ([#17057]). + Breaking changes ---------------- @@ -50,6 +53,10 @@ This section lists changes that do not have deprecation warnings. * `broadcast` now treats `Ref` (except for `Ptr`) arguments as 0-dimensional arrays ([#18965]). + * The runtime now enforces when new method definitions can take effect ([#17057]). + The flip-side of this is that new method definitions should now reliably actually + take effect, and be called when evaluating new code ([#265]). + Library improvements -------------------- From 51eaecee032b5c5d60044a8519d7f9ca2e35015f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 18 Dec 2016 21:58:16 -0500 Subject: [PATCH 8/8] increase timeout for rmprocs (workaround) node termination during node provisioning is not well handled, resulting in `connect: connection refused (ECONNREFUSED) in connect_to_worker from the new worker to the terminating worker for an example, see: https://travis-ci.org/JuliaLang/julia/jobs/186141590 --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index a1a7bd28c42df..7d2b65545df4a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -69,7 +69,7 @@ cd(dirname(@__FILE__)) do push!(results, (test, resp)) if (isa(resp[end], Integer) && (resp[end] > max_worker_rss)) || isa(resp, Exception) if n > 1 - rmprocs(p, waitfor=0.5) + rmprocs(p, waitfor=5.0) p = addprocs(1; exename=test_exename, exeflags=test_exeflags)[1] remotecall_fetch(()->include("testdefs.jl"), p) else