From bdd244ebc018661a45039b377f80eaf06c88db9d Mon Sep 17 00:00:00 2001 From: Yichao Yu Date: Sat, 10 Sep 2016 10:03:01 -0400 Subject: [PATCH 1/6] Replace all use of invoke to use Tuple arguments --- base/docs/helpdb/Base.jl | 4 ++-- base/sharedarray.jl | 6 +++--- examples/lru.jl | 2 +- test/core.jl | 11 ++--------- test/datafmt.jl | 2 +- test/dict.jl | 2 +- test/linalg/matmul.jl | 2 +- test/statistics.jl | 10 +++++----- 8 files changed, 16 insertions(+), 23 deletions(-) diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index c7a3004dcb789..de81445f463b4 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -1038,9 +1038,9 @@ An indexing operation into an array, `a`, tried to access an out-of-bounds eleme BoundsError """ - invoke(f, (types...), args...) + invoke(f, types <: Tuple, args...) -Invoke a method for the given generic function matching the specified types (as a tuple), on +Invoke a method for the given generic function matching the specified types, on the specified arguments. The arguments must be compatible with the specified types. This allows invoking a method other than the most specific matching method, which is useful when the behavior of a more general definition is explicitly needed (often as part of the diff --git a/base/sharedarray.jl b/base/sharedarray.jl index 54241d7cdf2cf..68c84921a626a 100644 --- a/base/sharedarray.jl +++ b/base/sharedarray.jl @@ -411,14 +411,14 @@ function serialize(s::AbstractSerializer, S::SharedArray) end function deserialize{T,N}(s::AbstractSerializer, t::Type{SharedArray{T,N}}) - S = invoke(deserialize, Tuple{AbstractSerializer, DataType}, s, t) + S = invoke(deserialize, Tuple{AbstractSerializer,DataType}, s, t) init_loc_flds(S, true) S end function show(io::IO, S::SharedArray) if length(S.s) > 0 - invoke(show, (IO, DenseArray), io, S) + invoke(show, Tuple{IO,DenseArray}, io, S) else show(io, remotecall_fetch(sharr->sharr.s, S.pids[1], S)) end @@ -426,7 +426,7 @@ end function show(io::IO, mime::MIME"text/plain", S::SharedArray) if length(S.s) > 0 - invoke(show, (IO, MIME"text/plain", DenseArray), io, MIME"text/plain"(), S) + invoke(show, Tuple{IO,MIME"text/plain",DenseArray}, io, MIME"text/plain"(), S) else # retrieve from the first worker mapping the array. println(io, summary(S), ":") diff --git a/examples/lru.jl b/examples/lru.jl index f665132524f78..4458032be03b7 100644 --- a/examples/lru.jl +++ b/examples/lru.jl @@ -110,7 +110,7 @@ end # Eviction function setindex!{V,K}(lru::BoundedLRU, v::V, key::K) - invoke(setindex!, (LRU, V, K), lru, v, key) + invoke(setindex!, Tuple{LRU,V,K}, lru, v, key) nrm = length(lru) - lru.maxsize for i in 1:nrm rm = pop!(lru.q) diff --git a/test/core.jl b/test/core.jl index 6560658cb0fac..455eed941c1ce 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2224,13 +2224,6 @@ f9520c(::Any, ::Any, ::Any, ::Any, ::Any, ::Any, args...) = 46 @test invoke(f9520b, Tuple{Any, Any, Any, Any, Any, Any}, 1, 2, 3, 4, 5, 6) == 23 @test invoke(f9520c, Tuple{Any, Any, Any, Any, Any, Any}, 1, 2, 3, 4, 5, 6) == 46 @test invoke(f9520c, Tuple{Any, Any, Any, Any, Any, Any, Any}, 1, 2, 3, 4, 5, 6, 7) == 46 -# Keep until the old signature of invoke is dropped. -@test invoke(f9520a, (Any, Any), 1, 2) == 15 -@test invoke(f9520a, (Any, Any, Any), 1, 2, 3) == 15 -@test invoke(f9520b, (Any, Any, Any), 1, 2, 3) == 23 -@test invoke(f9520b, (Any, Any, Any, Any, Any, Any), 1, 2, 3, 4, 5, 6) == 23 -@test invoke(f9520c, (Any, Any, Any, Any, Any, Any), 1, 2, 3, 4, 5, 6) == 46 -@test invoke(f9520c, (Any, Any, Any, Any, Any, Any, Any), 1, 2, 3, 4, 5, 6, 7) == 46 call_lambda1() = (()->x)(1) call_lambda2() = ((x)->x)() @@ -3672,7 +3665,7 @@ f14245() = (v = []; push!(v, length(v)); v) return y end end -foo9677(x::Array) = invoke(foo9677,(AbstractArray,),x) +foo9677(x::Array) = invoke(foo9677, Tuple{AbstractArray}, x) @test foo9677(1:5) == foo9677(randn(3)) # issue #6846 @@ -4191,7 +4184,7 @@ end # issue #8712 type Issue8712; end -@test isa(invoke(Issue8712, ()), Issue8712) +@test isa(invoke(Issue8712, Tuple{}), Issue8712) # issue #16089 f16089(args...) = typeof(args) diff --git a/test/datafmt.jl b/test/datafmt.jl index 0803b211f4c7b..df0900a702823 100644 --- a/test/datafmt.jl +++ b/test/datafmt.jl @@ -248,7 +248,7 @@ end @test sprint(io -> show(io, "text/csv", [1 2; 3 4])) == "1,2\n3,4\n" for writefunc in ((io,x) -> show(io, "text/csv", x), - (io,x) -> invoke(writedlm, (IO, Any, Any), io, x, ",")) + (io,x) -> invoke(writedlm, Tuple{IO,Any,Any}, io, x, ",")) # iterable collections of iterable rows: let x = [(1,2), (3,4)], io = IOBuffer() writefunc(io, x) diff --git a/test/dict.jl b/test/dict.jl index b901bca5bd8a3..8da4b67fbbfe4 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -509,7 +509,7 @@ end # filtering let d = Dict(zip(1:1000,1:1000)), f = (k,v) -> iseven(k) @test filter(f, d) == filter!(f, copy(d)) == - invoke(filter!, (Function, Associative), f, copy(d)) == + invoke(filter!, Tuple{Function,Associative}, f, copy(d)) == Dict(zip(2:2:1000, 2:2:1000)) end diff --git a/test/linalg/matmul.jl b/test/linalg/matmul.jl index 199921663bc7a..79810e8de329b 100644 --- a/test/linalg/matmul.jl +++ b/test/linalg/matmul.jl @@ -259,7 +259,7 @@ for elty in (Float32,Float64,Complex64,Complex128) @test x.'*y == convert(Vector{elty},[29.0]) end -vecdot_(x,y) = invoke(vecdot, (Any,Any), x,y) # generic vecdot +vecdot_(x,y) = invoke(vecdot, Tuple{Any,Any}, x,y) # generic vecdot let AA = [1+2im 3+4im; 5+6im 7+8im], BB = [2+7im 4+1im; 3+8im 6+5im] for Atype = ["Array", "SubArray"], Btype = ["Array", "SubArray"] A = Atype == "Array" ? AA : view(AA, 1:2, 1:2) diff --git a/test/statistics.jl b/test/statistics.jl index 18928f4e31b10..4569993eed8e3 100644 --- a/test/statistics.jl +++ b/test/statistics.jl @@ -47,7 +47,7 @@ X = [2 3 1 -1; 7 4 5 -4] @test median!([1 2; 3 4]) == 2.5 -@test invoke(median, (AbstractVector,), 1:10) == median(1:10) == 5.5 +@test invoke(median, Tuple{AbstractVector}, 1:10) == median(1:10) == 5.5 # mean @test_throws ArgumentError mean(()) @@ -338,14 +338,14 @@ y = [0.40003674665581906,0.4085630862624367,0.41662034698690303,0.41662034698690 # variance of complex arrays (#13309) let z = rand(Complex128, 10) - @test var(z) ≈ invoke(var, (Any,), z) ≈ cov(z) ≈ var(z,1)[1] ≈ sum(abs2, z - mean(z))/9 + @test var(z) ≈ invoke(var, Tuple{Any}, z) ≈ cov(z) ≈ var(z,1)[1] ≈ sum(abs2, z - mean(z))/9 @test isa(var(z), Float64) - @test isa(invoke(var, (Any,), z), Float64) + @test isa(invoke(var, Tuple{Any}, z), Float64) @test isa(cov(z), Float64) @test isa(var(z,1), Vector{Float64}) - @test varm(z, 0.0) ≈ invoke(varm, (Any,Float64), z, 0.0) ≈ sum(abs2, z)/9 + @test varm(z, 0.0) ≈ invoke(varm, Tuple{Any,Float64}, z, 0.0) ≈ sum(abs2, z)/9 @test isa(varm(z, 0.0), Float64) - @test isa(invoke(varm, (Any,Float64), z, 0.0), Float64) + @test isa(invoke(varm, Tuple{Any,Float64}, z, 0.0), Float64) @test cor(z) === 1.0 end let v = varm([1.0+2.0im], 0; corrected = false) From 46df749671eeba8831548ae1e020bd2982d533b0 Mon Sep 17 00:00:00 2001 From: Yichao Yu Date: Sat, 10 Sep 2016 11:44:11 -0400 Subject: [PATCH 2/6] Deprecate invoke with tuple argument --- src/array.c | 9 ++------- src/builtins.c | 23 ++++++++++++++++++++++- src/julia_internal.h | 1 + 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/array.c b/src/array.c index 37e8406e231ab..9d5f09611994b 100644 --- a/src/array.c +++ b/src/array.c @@ -529,13 +529,8 @@ JL_DLLEXPORT int jl_array_isassigned(jl_array_t *a, size_t i) int jl_array_isdefined(jl_value_t **args0, int nargs) { assert(jl_is_array(args0[0])); - jl_value_t **depwarn_args; - JL_GC_PUSHARGS(depwarn_args, 3); - depwarn_args[0] = jl_get_global(jl_base_module, jl_symbol("depwarn")); - depwarn_args[1] = jl_cstr_to_string("isdefined(a::Array, i::Int) is deprecated, use isassigned(a, i) instead"); - depwarn_args[2] = (jl_value_t*) jl_symbol("isdefined"); - jl_apply(depwarn_args, 3); - JL_GC_POP(); + jl_depwarn("`isdefined(a::Array, i::Int)` is deprecated, " + "use `isassigned(a, i)` instead", jl_symbol("isdefined")); jl_array_t *a = (jl_array_t*)args0[0]; jl_value_t **args = &args0[1]; diff --git a/src/builtins.c b/src/builtins.c index 6b112f2c557a5..00c7806dda4cb 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1023,7 +1023,9 @@ JL_CALLABLE(jl_f_invoke) jl_value_t *argtypes = args[1]; JL_GC_PUSH1(&argtypes); if (jl_is_tuple(args[1])) { - // TODO: maybe deprecation warning, better checking + jl_depwarn("`invoke(f, (types...), ...)` is deprecated, " + "use `invoke(f, Tuple{types...}, ...)` instead", + jl_symbol("invoke")); argtypes = (jl_value_t*)jl_apply_tuple_type_v((jl_value_t**)jl_data_ptr(argtypes), jl_nfields(argtypes)); } @@ -1640,6 +1642,25 @@ JL_DLLEXPORT void jl_breakpoint(jl_value_t *v) // put a breakpoint in your debugger here } +void jl_depwarn(const char *msg, jl_sym_t *sym) +{ + static jl_value_t *depwarn_func = NULL; + if (!depwarn_func && jl_base_module) { + depwarn_func = jl_get_global(jl_base_module, jl_symbol("depwarn")); + } + if (!depwarn_func) { + jl_safe_printf("WARNING: %s\n", msg); + return; + } + jl_value_t **depwarn_args; + JL_GC_PUSHARGS(depwarn_args, 3); + depwarn_args[0] = depwarn_func; + depwarn_args[1] = jl_cstr_to_string(msg); + depwarn_args[2] = (jl_value_t*)sym; + jl_apply(depwarn_args, 3); + JL_GC_POP(); +} + #ifdef __cplusplus } #endif diff --git a/src/julia_internal.h b/src/julia_internal.h index 38c94c1b9974c..ce2ec12d5df36 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -829,6 +829,7 @@ STATIC_INLINE void *jl_get_frame_addr(void) } JL_DLLEXPORT jl_array_t *jl_array_cconvert_cstring(jl_array_t *a); +void jl_depwarn(const char *msg, jl_sym_t *sym); int isabspath(const char *in); From 153e514a38f9e54e27e1130b0c585b35561a4788 Mon Sep 17 00:00:00 2001 From: Yichao Yu Date: Sat, 10 Sep 2016 08:26:48 -0400 Subject: [PATCH 3/6] Make invoke_NF a top level function so that it is slightly easier to reason about --- base/inference.jl | 225 +++++++++++++++++++++++----------------------- 1 file changed, 114 insertions(+), 111 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 3eddc143a9aaa..18cfce8047c6f 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -2715,6 +2715,114 @@ function countunionsplit(atypes::Vector{Any}) return nu end +function invoke_NF(e::Expr, atypes::Vector{Any}, sv::InferenceState, + atype_unlimited::ANY) + # converts a :call to :invoke + local nu = countunionsplit(atypes) + nu > sv.params.MAX_UNION_SPLITTING && return NF + + if nu > 1 + local spec_hit = nothing + local spec_miss = nothing + local error_label = nothing + local linfo_var = add_slot!(sv.src, MethodInstance, false) + local ex = copy(e) + local stmts = [] + local arg_hoisted = false + for i = length(atypes):-1:1; local i + local ti = atypes[i] + if arg_hoisted || isa(ti, Union) + aei = ex.args[i] + if !effect_free(aei, sv.src, sv.mod, false) + arg_hoisted = true + newvar = newvar!(sv, ti) + unshift!(stmts, :($newvar = $aei)) + ex.args[i] = newvar + end + end + end + 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, 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)) + push!(stmt, GotoNode(spec_hit.label)) + return stmt + else + local ti = atypes[i] + if isa(ti, Union) + local all = true + local stmts = [] + local aei = ex.args[i] + for ty in (ti::Union).types + local ty + atypes[i] = ty + local match = splitunion(atypes, i - 1) + if match !== false + after = genlabel(sv) + unshift!(match, Expr(:gotoifnot, Expr(:call, GlobalRef(Core, :isa), aei, ty), after.label)) + append!(stmts, match) + push!(stmts, after) + else + all = false + end + end + if UNION_SPLIT_MISMATCH_ERROR && all + error_label === nothing && (error_label = genlabel(sv)) + push!(stmts, GotoNode(error_label.label)) + else + spec_miss === nothing && (spec_miss = genlabel(sv)) + push!(stmts, GotoNode(spec_miss.label)) + end + atypes[i] = ti + return isempty(stmts) ? false : stmts + else + return splitunion(atypes, i - 1) + end + end + end + local match = splitunion(atypes, length(atypes)) + if match !== false && spec_hit !== nothing + append!(stmts, match) + if error_label !== nothing + push!(stmts, error_label) + 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 + ret_var = add_slot!(sv.src, widenconst(ex.typ), false) + merge = genlabel(sv) + push!(stmts, spec_miss) + push!(stmts, Expr(:(=), ret_var, ex)) + push!(stmts, GotoNode(merge.label)) + else + ret_var = newvar!(sv, ex.typ) + end + push!(stmts, spec_hit) + ex = copy(ex) + ex.head = :invoke + unshift!(ex.args, linfo_var) + push!(stmts, Expr(:(=), ret_var, ex)) + if spec_miss !== nothing + push!(stmts, merge) + end + return (ret_var, stmts) + end + else + 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 + end + return NF +end + # inline functions whose bodies are "inline_worthy" # where the function body doesn't contain any argument more than once. # static parameters are ok if all the static parameter values are leaf types, @@ -2770,113 +2878,8 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end local atype_unlimited = argtypes_to_type(atypes) - function invoke_NF() - # converts a :call to :invoke - local nu = countunionsplit(atypes) - nu > sv.params.MAX_UNION_SPLITTING && return NF - - if nu > 1 - local spec_hit = nothing - local spec_miss = nothing - local error_label = nothing - local linfo_var = add_slot!(sv.src, MethodInstance, false) - local ex = copy(e) - local stmts = [] - local arg_hoisted = false - for i = length(atypes):-1:1; local i - local ti = atypes[i] - if arg_hoisted || isa(ti, Union) - aei = ex.args[i] - if !effect_free(aei, sv.src, sv.mod, false) - arg_hoisted = true - newvar = newvar!(sv, ti) - insert!(stmts, 1, Expr(:(=), newvar, aei)) - ex.args[i] = newvar - end - end - end - 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, 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)) - push!(stmt, GotoNode(spec_hit.label)) - return stmt - else - local ti = atypes[i] - if isa(ti, Union) - local all = true - local stmts = [] - local aei = ex.args[i] - for ty in (ti::Union).types; local ty - atypes[i] = ty - local match = splitunion(atypes, i - 1) - if match !== false - after = genlabel(sv) - unshift!(match, Expr(:gotoifnot, Expr(:call, GlobalRef(Core, :isa), aei, ty), after.label)) - append!(stmts, match) - push!(stmts, after) - else - all = false - end - end - if UNION_SPLIT_MISMATCH_ERROR && all - error_label === nothing && (error_label = genlabel(sv)) - push!(stmts, GotoNode(error_label.label)) - else - spec_miss === nothing && (spec_miss = genlabel(sv)) - push!(stmts, GotoNode(spec_miss.label)) - end - atypes[i] = ti - return isempty(stmts) ? false : stmts - else - return splitunion(atypes, i - 1) - end - end - end - local match = splitunion(atypes, length(atypes)) - if match !== false && spec_hit !== nothing - append!(stmts, match) - if error_label !== nothing - push!(stmts, error_label) - 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 - ret_var = add_slot!(sv.src, widenconst(ex.typ), false) - merge = genlabel(sv) - push!(stmts, spec_miss) - push!(stmts, Expr(:(=), ret_var, ex)) - push!(stmts, GotoNode(merge.label)) - else - ret_var = newvar!(sv, ex.typ) - end - push!(stmts, spec_hit) - ex = copy(ex) - ex.head = :invoke - unshift!(ex.args, linfo_var) - push!(stmts, Expr(:(=), ret_var, ex)) - if spec_miss !== nothing - push!(stmts, merge) - end - return (ret_var, stmts) - end - else - 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 - end - return NF - end if !sv.params.inlining - return invoke_NF() + return invoke_NF(e, atypes, sv, atype_unlimited) end if length(atype_unlimited.parameters) - 1 > sv.params.MAX_TUPLETYPE_LEN @@ -2888,7 +2891,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference 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() + return invoke_NF(e, atypes, sv, atype_unlimited) end meth = meth[1]::SimpleVector metharg = meth[1]::Type @@ -2905,7 +2908,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference methsig = method.sig if !(atype <: metharg) - return invoke_NF() + return invoke_NF(e, atypes, sv, atype_unlimited) end na = method.nargs @@ -2952,7 +2955,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference # see if the method has been previously inferred (and cached) linfo = code_for_method(method, metharg, methsp, sv.params.world, !force_infer) # Union{Void, MethodInstance} - isa(linfo, MethodInstance) || return invoke_NF() + isa(linfo, MethodInstance) || return invoke_NF(e, atypes, sv, atype_unlimited) linfo = linfo::MethodInstance if linfo.jlcall_api == 2 # in this case function can be inlined to a constant @@ -3017,11 +3020,11 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end # check that the code is inlineable - isa(src, CodeInfo) || return invoke_NF() + isa(src, CodeInfo) || return invoke_NF(e, atypes, sv, atype_unlimited) src = src::CodeInfo ast = src.code if !src.inferred || !src.inlineable - return invoke_NF() + return invoke_NF(e, atypes, sv, atype_unlimited) end # create the backedge From a430c0791085636a37e33f91a235bb0d6a7a82c8 Mon Sep 17 00:00:00 2001 From: Yichao Yu Date: Sat, 10 Sep 2016 16:11:27 -0400 Subject: [PATCH 4/6] Make invoke_NF more compatible with invoke --- base/inference.jl | 50 +++++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 18cfce8047c6f..6fdfb754ff56e 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -2715,22 +2715,23 @@ function countunionsplit(atypes::Vector{Any}) return nu end -function invoke_NF(e::Expr, atypes::Vector{Any}, sv::InferenceState, - atype_unlimited::ANY) +function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY) # converts a :call to :invoke - local nu = countunionsplit(atypes) + nu = countunionsplit(atypes) nu > sv.params.MAX_UNION_SPLITTING && return NF if nu > 1 - local spec_hit = nothing - local spec_miss = nothing - local error_label = nothing - local linfo_var = add_slot!(sv.src, MethodInstance, false) - local ex = copy(e) - local stmts = [] - local arg_hoisted = false - for i = length(atypes):-1:1; local i - local ti = atypes[i] + spec_hit = nothing + spec_miss = nothing + error_label = nothing + linfo_var = add_slot!(sv.src, MethodInstance, false) + ex = Expr(:call) + ex.args = copy(argexprs) + ex.typ = etype + stmts = [] + arg_hoisted = false + for i = length(atypes):-1:1 + ti = atypes[i] if arg_hoisted || isa(ti, Union) aei = ex.args[i] if !effect_free(aei, sv.src, sv.mod, false) @@ -2816,9 +2817,11 @@ function invoke_NF(e::Expr, atypes::Vector{Any}, sv::InferenceState, 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 + unshift!(argexprs, cache_linfo) + ex = Expr(:invoke) + ex.args = argexprs + ex.typ = etype + return ex end return NF end @@ -2835,14 +2838,14 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference # typeassert(x::S, T) => x, when S<:T if isType(atypes[3]) && isleaftype(atypes[3]) && atypes[2] ⊑ atypes[3].parameters[1] - return (e.args[2],()) + return (argexprs[2],()) end end if length(atypes)==3 && f === unbox at3 = widenconst(atypes[3]) if isa(at3,DataType) && !at3.mutable && at3.layout != C_NULL && datatype_pointerfree(at3) # remove redundant unbox - return (e.args[3],()) + return (argexprs[3],()) end end topmod = _topmod(sv) @@ -2879,7 +2882,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference local atype_unlimited = argtypes_to_type(atypes) if !sv.params.inlining - return invoke_NF(e, atypes, sv, atype_unlimited) + return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited) end if length(atype_unlimited.parameters) - 1 > sv.params.MAX_TUPLETYPE_LEN @@ -2891,7 +2894,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference 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(e, atypes, sv, atype_unlimited) + return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited) end meth = meth[1]::SimpleVector metharg = meth[1]::Type @@ -2908,9 +2911,10 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference methsig = method.sig if !(atype <: metharg) - return invoke_NF(e, atypes, sv, atype_unlimited) + return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited) end + argexprs0 = argexprs na = method.nargs # check for vararg function isva = false @@ -2955,7 +2959,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference # see if the method has been previously inferred (and cached) linfo = code_for_method(method, metharg, methsp, sv.params.world, !force_infer) # Union{Void, MethodInstance} - isa(linfo, MethodInstance) || return invoke_NF(e, atypes, sv, atype_unlimited) + isa(linfo, MethodInstance) || return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited) linfo = linfo::MethodInstance if linfo.jlcall_api == 2 # in this case function can be inlined to a constant @@ -3020,11 +3024,11 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end # check that the code is inlineable - isa(src, CodeInfo) || return invoke_NF(e, atypes, sv, atype_unlimited) + isa(src, CodeInfo) || return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited) src = src::CodeInfo ast = src.code if !src.inferred || !src.inlineable - return invoke_NF(e, atypes, sv, atype_unlimited) + return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited) end # create the backedge From 4d7a0540cd5b4f8ec946eca4eb66b270544e3c5d Mon Sep 17 00:00:00 2001 From: Yichao Yu Date: Sun, 11 Sep 2016 07:45:18 -0400 Subject: [PATCH 5/6] Inline `invoke` (take 3) Fix #9608 --- base/inference.jl | 180 +++++++++++++++++++++++++++++++++++++--------- src/gf.c | 85 +++++++++++++++++++++- 2 files changed, 231 insertions(+), 34 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 6fdfb754ff56e..d96c2922a073f 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -736,8 +736,8 @@ function invoke_tfunc(f::ANY, types::ANY, argtype::ANY, sv::InferenceState) return Any end meth = entry.func - (ti, env) = ccall(:jl_match_method, Any, (Any, Any, Any), - argtype, meth.sig, meth.tvars)::SimpleVector + (ti, env) = ccall(:jl_match_method, Ref{SimpleVector}, (Any, Any, Any), + argtype, meth.sig, meth.tvars) return typeinf_edge(meth::Method, ti, env, sv) end @@ -2693,14 +2693,34 @@ end #### post-inference optimizations #### -function inline_as_constant(val::ANY, argexprs, sv::InferenceState) +immutable InvokeData + mt::MethodTable + entry::TypeMapEntry + types0 + fexpr + texpr +end + +function inline_as_constant(val::ANY, argexprs, sv::InferenceState, + invoke_data::ANY) + if invoke_data === nothing + invoke_fexpr = nothing + invoke_texpr = nothing + else + invoke_data = invoke_data::InvokeData + invoke_fexpr = invoke_data.fexpr + invoke_texpr = invoke_data.texpr + end # check if any arguments aren't effect_free and need to be kept around - stmts = Any[] + stmts = invoke_fexpr === nothing ? [] : Any[invoke_fexpr] for i = 1:length(argexprs) arg = argexprs[i] if !effect_free(arg, sv.src, sv.mod, false) push!(stmts, arg) end + if i == 1 && !(invoke_texpr === nothing) + push!(stmts, invoke_texpr) + end end return (QuoteNode(val), stmts) end @@ -2715,10 +2735,30 @@ function countunionsplit(atypes::Vector{Any}) return nu end -function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY) +function get_spec_lambda(atypes::ANY, sv, invoke_data::ANY) + if invoke_data === nothing + return ccall(:jl_get_spec_lambda, Any, (Any, UInt), atypes, sv.params.world) + else + invoke_data = invoke_data::InvokeData + atypes <: invoke_data.types0 || return nothing + return ccall(:jl_get_invoke_lambda, Any, (Any, Any, Any, UInt), + invoke_data.mt, invoke_data.entry, atypes, sv.params.world) + end +end + +function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY, + invoke_data::ANY) # converts a :call to :invoke nu = countunionsplit(atypes) nu > sv.params.MAX_UNION_SPLITTING && return NF + if invoke_data === nothing + invoke_fexpr = nothing + invoke_texpr = nothing + else + invoke_data = invoke_data::InvokeData + invoke_fexpr = invoke_data.fexpr + invoke_texpr = invoke_data.texpr + end if nu > 1 spec_hit = nothing @@ -2731,6 +2771,10 @@ function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY) stmts = [] arg_hoisted = false for i = length(atypes):-1:1 + if i == 1 && !(invoke_texpr === nothing) + unshift!(stmts, invoke_texpr) + arg_hoisted = true + end ti = atypes[i] if arg_hoisted || isa(ti, Union) aei = ex.args[i] @@ -2742,10 +2786,11 @@ function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY) end end end + invoke_fexpr === nothing || unshift!(stmts, invoke_fexpr) 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, UInt), sig, sv.params.world) + local li = get_spec_lambda(sig, sv, invoke_data) li === nothing && return false add_backedge(li, sv) local stmt = [] @@ -2814,14 +2859,25 @@ function invoke_NF(argexprs, etype::ANY, atypes, sv, atype_unlimited::ANY) return (ret_var, stmts) end else - local cache_linfo = ccall(:jl_get_spec_lambda, Any, (Any, UInt), atype_unlimited, sv.params.world) + local cache_linfo = get_spec_lambda(atype_unlimited, sv, invoke_data) cache_linfo === nothing && return NF add_backedge(cache_linfo, sv) unshift!(argexprs, cache_linfo) ex = Expr(:invoke) ex.args = argexprs ex.typ = etype - return ex + if invoke_texpr === nothing + if invoke_fexpr === nothing + return ex + else + return ex, Any[invoke_fexpr] + end + end + newvar = newvar!(sv, atypes[1]) + stmts = Any[invoke_fexpr, :($newvar = $(argexprs[1])), + invoke_texpr] + argexprs[1] = newvar + return ex, stmts end return NF end @@ -2875,14 +2931,55 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end end - if isa(f, IntrinsicFunction) || ft ⊑ IntrinsicFunction || + invoke_data = nothing + invoke_fexpr = nothing + invoke_texpr = nothing + if f === Core.invoke && length(atypes) >= 3 + ft = widenconst(atypes[2]) + invoke_tt = widenconst(atypes[3]) + if !isleaftype(ft) || !isleaftype(invoke_tt) || !isType(invoke_tt) + return NF + end + if !(isa(invoke_tt.parameters[1], Type) && + invoke_tt.parameters[1] <: Tuple) + return NF + end + invoke_tt_params = invoke_tt.parameters[1].parameters + invoke_types = Tuple{ft, invoke_tt_params...} + invoke_entry = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), + invoke_types, sv.params.world) + invoke_entry === nothing && return NF + invoke_fexpr = argexprs[1] + invoke_texpr = argexprs[3] + if effect_free(invoke_fexpr, sv.src, sv.mod, false) + invoke_fexpr = nothing + end + if effect_free(invoke_texpr, sv.src, sv.mod, false) + invoke_fexpr = nothing + end + invoke_data = InvokeData(ft.name.mt, invoke_entry, + invoke_types, invoke_fexpr, invoke_texpr) + atype0 = atypes[2] + argexpr0 = argexprs[2] + atypes = atypes[4:end] + argexprs = argexprs[4:end] + unshift!(atypes, atype0) + unshift!(argexprs, argexpr0) + f = isdefined(ft, :instance) ? ft.instance : nothing + elseif isa(f, IntrinsicFunction) || ft ⊑ IntrinsicFunction || isa(f, Builtin) || ft ⊑ Builtin return NF end - local atype_unlimited = argtypes_to_type(atypes) + atype_unlimited = argtypes_to_type(atypes) + if !(invoke_data === nothing) + invoke_data = invoke_data::InvokeData + # TODO emit a type check and proceed for this case + atype_unlimited <: invoke_data.types0 || return NF + end if !sv.params.inlining - return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited) + return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited, + invoke_data) end if length(atype_unlimited.parameters) - 1 > sv.params.MAX_TUPLETYPE_LEN @@ -2890,28 +2987,41 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference else atype = atype_unlimited end - 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(argexprs, e.typ, atypes, sv, atype_unlimited) + if invoke_data === nothing + 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(argexprs, e.typ, atypes, sv, + atype_unlimited, invoke_data) + end + meth = meth[1]::SimpleVector + metharg = meth[1]::Type + methsp = meth[2]::SimpleVector + method = meth[3]::Method + else + invoke_data = invoke_data::InvokeData + method = invoke_data.entry.func + (metharg, methsp) = ccall(:jl_match_method, Ref{SimpleVector}, + (Any, Any, Any), + atype_unlimited, method.sig, method.tvars) + methsp = methsp::SimpleVector end - meth = meth[1]::SimpleVector - metharg = meth[1]::Type - methsp = meth[2] - method = meth[3]::Method # check whether call can be inlined to just a quoted constant value if isa(f, widenconst(ft)) && !method.isstaged && (method.source.pure || f === return_type) if isconstType(e.typ,false) - return inline_as_constant(e.typ.parameters[1], argexprs, sv) + return inline_as_constant(e.typ.parameters[1], argexprs, sv, + invoke_data) elseif isa(e.typ,Const) - return inline_as_constant(e.typ.val, argexprs, sv) + return inline_as_constant(e.typ.val, argexprs, sv, + invoke_data) end end methsig = method.sig if !(atype <: metharg) - return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited) + return invoke_NF(argexprs, e.typ, atypes, sv, atype_unlimited, + invoke_data) end argexprs0 = argexprs @@ -2959,12 +3069,13 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference # see if the method has been previously inferred (and cached) linfo = code_for_method(method, metharg, methsp, sv.params.world, !force_infer) # Union{Void, MethodInstance} - isa(linfo, MethodInstance) || return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited) + isa(linfo, MethodInstance) || return invoke_NF(argexprs0, e.typ, atypes, sv, + atype_unlimited, invoke_data) 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) + return inline_as_constant(linfo.inferred, argexprs, sv, invoke_data) end # see if the method has a current InferenceState frame @@ -3016,7 +3127,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference @assert isconstType(frame.bestguess, true) inferred_const = frame.bestguess.parameters[1] end - return inline_as_constant(inferred_const, argexprs, sv) + return inline_as_constant(inferred_const, argexprs, sv, invoke_data) end rettype = widenconst(frame.bestguess) else @@ -3024,11 +3135,13 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end # check that the code is inlineable - isa(src, CodeInfo) || return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited) + isa(src, CodeInfo) || return invoke_NF(argexprs0, e.typ, atypes, sv, + atype_unlimited, invoke_data) src = src::CodeInfo ast = src.code if !src.inferred || !src.inlineable - return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited) + return invoke_NF(argexprs0, e.typ, atypes, sv, atype_unlimited, + invoke_data) end # create the backedge @@ -3051,8 +3164,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end - methargs = metharg.parameters - nm = length(methargs) + nm = length(metharg.parameters) if !isa(ast, Array{Any,1}) ast = ccall(:jl_uncompress_ast, Any, (Any, Any), method, ast) @@ -3066,14 +3178,17 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference propagate_inbounds = src.propagate_inbounds # see if each argument occurs only once in the body expression - stmts = Any[] - prelude_stmts = Any[] + stmts = [] + prelude_stmts = [] stmts_free = true # true = all entries of stmts are effect_free for i=na:-1:1 # stmts_free needs to be calculated in reverse-argument order #args_i = args[i] aei = argexprs[i] aeitype = argtype = widenconst(exprtype(aei, sv.src, sv.mod)) + if i == 1 && !(invoke_texpr === nothing) + unshift!(prelude_stmts, invoke_texpr) + end # ok for argument to occur more than once if the actual argument # is a symbol or constant, or is not affected by previous statements @@ -3107,6 +3222,7 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference end end end + invoke_fexpr === nothing || unshift!(prelude_stmts, invoke_fexpr) # re-number the SSAValues and copy their type-info to the new ast ssavalue_types = src.ssavaluetypes diff --git a/src/gf.c b/src/gf.c index ac4675bbaf067..da41cb5ca0e0d 100644 --- a/src/gf.c +++ b/src/gf.c @@ -2222,11 +2222,10 @@ 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; jl_tupletype_t *types = NULL; jl_tupletype_t *sig = NULL; - JL_GC_PUSH5(&types, &tpenv, &newsig, &sig, &tt); + JL_GC_PUSH4(&types, &tpenv, &sig, &tt); 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); @@ -2277,6 +2276,88 @@ jl_value_t *jl_gf_invoke(jl_tupletype_t *types0, jl_value_t **args, size_t nargs return jl_call_method_internal(mfunc, args, nargs); } +typedef struct _tupletype_stack_t { + struct _tupletype_stack_t *parent; + jl_tupletype_t *tt; +} tupletype_stack_t; + +static int tupletype_on_stack(jl_tupletype_t *tt, tupletype_stack_t *stack) +{ + while (stack) { + if (tt == stack->tt) + return 1; + stack = stack->parent; + } + return 0; +} + +static int tupletype_has_datatype(jl_tupletype_t *tt, tupletype_stack_t *stack) +{ + for (int i = 0; i < jl_nparams(tt); i++) { + jl_value_t *ti = jl_tparam(tt, i); + if (ti == (jl_value_t*)jl_datatype_type) + return 1; + if (jl_is_tuple_type(ti)) { + jl_tupletype_t *tt1 = (jl_tupletype_t*)ti; + if (!tupletype_on_stack(tt1, stack) && + tupletype_has_datatype(tt1, stack)) { + return 1; + } + } + } + return 0; +} + +JL_DLLEXPORT jl_value_t *jl_get_invoke_lambda(jl_methtable_t *mt, + jl_typemap_entry_t *entry, + jl_tupletype_t *tt, + size_t world) +{ + if (!jl_is_leaf_type((jl_value_t*)tt) || tupletype_has_datatype(tt, NULL)) + return jl_nothing; + + jl_method_t *method = entry->func.method; + jl_typemap_entry_t *tm = NULL; + if (method->invokes.unknown != NULL) { + tm = jl_typemap_assoc_by_type(method->invokes, tt, NULL, 0, 1, + jl_cachearg_offset(mt), world); + if (tm) { + return (jl_value_t*)tm->func.linfo; + } + } + + JL_LOCK(&method->writelock); + if (method->invokes.unknown != NULL) { + tm = jl_typemap_assoc_by_type(method->invokes, tt, NULL, 0, 1, + jl_cachearg_offset(mt), world); + if (tm) { + jl_method_instance_t *mfunc = tm->func.linfo; + JL_UNLOCK(&method->writelock); + return (jl_value_t*)mfunc; + } + } + jl_svec_t *tpenv = jl_emptysvec; + jl_tupletype_t *sig = NULL; + JL_GC_PUSH2(&tpenv, &sig); + if (entry->tvars != jl_emptysvec) { + jl_value_t *ti = + jl_lookup_match((jl_value_t*)tt, (jl_value_t*)entry->sig, &tpenv, entry->tvars); + assert(ti != (jl_value_t*)jl_bottom_type); + (void)ti; + } + sig = join_tsig(tt, entry->sig); + jl_method_t *func = entry->func.method; + + if (func->invokes.unknown == NULL) + func->invokes.unknown = jl_nothing; + + jl_method_instance_t *mfunc = cache_method(mt, &func->invokes, entry->func.value, + sig, tt, entry, world, tpenv, 1); + JL_GC_POP(); + JL_UNLOCK(&method->writelock); + return (jl_value_t*)mfunc; +} + static jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st, int iskw) { // type name is function name prefixed with # From b1c47ec87a6fad4ed284ecf5658d26623873f74f Mon Sep 17 00:00:00 2001 From: Yichao Yu Date: Mon, 9 Jan 2017 07:06:14 +0800 Subject: [PATCH 6/6] More invoke tests --- test/core.jl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/core.jl b/test/core.jl index 455eed941c1ce..cf72db7f763e6 100644 --- a/test/core.jl +++ b/test/core.jl @@ -4652,6 +4652,21 @@ catch e (e::ErrorException).msg end == "generated function body is not pure. this likely means it contains a closure or comprehension." +let x = 1 + global g18444 + @noinline g18444(a) = (x += 1; a[]) + f18444_1(a) = invoke(sin, Tuple{Int}, g18444(a)) + f18444_2(a) = invoke(sin, Tuple{Integer}, g18444(a)) + @test_throws ErrorException f18444_1(Ref{Any}(1.0)) + @test x == 2 + @test_throws ErrorException f18444_2(Ref{Any}(1.0)) + @test x == 3 + @test f18444_1(Ref{Any}(1)) === sin(1) + @test x == 4 + @test f18444_2(Ref{Any}(1)) === sin(1) + @test x == 5 +end + # issue #10981, long argument lists let a = fill(["sdf"], 2*10^6), temp_vcat(x...) = vcat(x...) # we introduce a new function `temp_vcat` to make sure there is no existing