From 1fd50a098bc672329a8ea947343a058466e24315 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 9 Oct 2015 04:04:25 -0400 Subject: [PATCH 1/5] implement @pure annotation (recorded in the linfo) and support in inference such that pure functions might be run at that time --- base/expr.jl | 12 ++- base/inference.jl | 189 ++++++++++++++++++++++++++++++++++------------ src/alloc.c | 1 + src/dump.c | 2 + src/interpreter.c | 3 + src/jltypes.c | 9 ++- src/julia.h | 1 + 7 files changed, 156 insertions(+), 61 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index 9a322edc9c573..8378e53e517ad 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -67,18 +67,16 @@ macro eval(x) end macro inline(ex) - esc(_inline(ex)) + esc(isa(ex, Expr) ? pushmeta!(ex, :inline) : ex) end -_inline(ex::Expr) = pushmeta!(ex, :inline) -_inline(arg) = arg - macro noinline(ex) - esc(_noinline(ex)) + esc(isa(ex, Expr) ? pushmeta!(ex, :noinline) : ex) end -_noinline(ex::Expr) = pushmeta!(ex, :noinline) -_noinline(arg) = arg +macro pure(ex) + esc(isa(ex, Expr) ? pushmeta!(ex, :pure) : ex) +end ## some macro utilities ## diff --git a/base/inference.jl b/base/inference.jl index e4e5fa17d4a67..49a1115751781 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -6,7 +6,7 @@ const MAX_TYPE_DEPTH = 5 const MAX_TUPLETYPE_LEN = 8 const MAX_TUPLE_DEPTH = 4 -type NotFound +immutable NotFound end const NF = NotFound() @@ -534,7 +534,7 @@ function builtin_tfunction(f::ANY, args::ANY, argtype::ANY, vtypes::ObjectIdDict return tf[3](argtypes...) end -function isconstantfunc(f::ANY, sv::StaticVarInfo) +function isconstantref(f::ANY, sv::StaticVarInfo) if isa(f,TopNode) m = _topmod() return isconst(m, f.name) && isdefined(m, f.name) && f @@ -543,39 +543,46 @@ function isconstantfunc(f::ANY, sv::StaticVarInfo) M = f.mod; s = f.name return isdefined(M,s) && isconst(M,s) && f end - if isa(f,Expr) && is(f.head,:call) - if length(f.args) == 3 && isa(f.args[1], TopNode) && - is(f.args[1].name,:getfield) && isa(f.args[3],QuoteNode) - s = f.args[3].value - if isa(f.args[2],Module) - M = f.args[2] - else - M = isconstantfunc(f.args[2], sv) - if M === false - return false - end - M = _ieval(M) - if !isa(M,Module) - return false + if isa(f,Expr) + if is(f.head,:call) + if length(f.args) == 3 && isa(f.args[1], TopNode) && + is(f.args[1].name,:getfield) && isa(f.args[3],QuoteNode) + s = f.args[3].value + if isa(f.args[2],Module) + M = f.args[2] + else + M = isconstantref(f.args[2], sv) + if M === false + return false + end + M = _ieval(M) + if !isa(M,Module) + return false + end end + return isdefined(M,s) && isconst(M,s) && f end - return isdefined(M,s) && isconst(M,s) && f + elseif is(f.head,:inert) + return f end + return false end - - if isa(f,QuoteNode) && (isa(f.value, Function) || isa(f.value, IntrinsicFunction)) - return f.value - end - if isa(f,Function) || isa(f,IntrinsicFunction) + if isa(f,QuoteNode) return f end if isa(f,SymbolNode) f = f.name end - return isa(f,Symbol) && is_global(sv, f) && _iisconst(f) && f + if isa(f,Symbol) + return is_global(sv, f) && _iisconst(f) && f + end + if isa(f,GenSym) || isa(f,LambdaStaticData) + return false + end + return f end -const isconstantref = isconstantfunc +const isconstantfunc = isconstantref const limit_tuple_depth = t->limit_tuple_depth_(t,0) @@ -650,25 +657,7 @@ function abstract_call_gf(f, fargs, argtype, e) end end if istopfunction(tm, f, :promote_type) || istopfunction(tm, f, :typejoin) - la = length(argtypes) - c = cell(la) - for i = 1:la - t = argtypes[i] - if isType(t) && !isa(t.parameters[1],TypeVar) - c[i] = t.parameters[1] - else - return Type - end - end - if istopfunction(tm, f, :promote_type) - try - RT = Type{f(c...)} - return RT - catch - end - else - return Type{f(c...)} - end + return Type end # don't consider more than N methods. this trades off between # compiler performance and generated code performance. @@ -821,7 +810,100 @@ function abstract_apply(af, fargs, aargtypes::Vector{Any}, vtypes, sv, e) return abstract_call(af, (), Any[Vararg{Any}], vtypes, sv, ()) end +function isconstantargs(args, argtypes::Vector{Any}, sv::StaticVarInfo) + if isempty(argtypes) + return true + end + if is(args,()) || isa(argtypes[end], Vararg) + return false + end + for i = 1:length(args) + arg = args[i] + t = argtypes[i] + if !effect_free(arg, sv, false) # TODO: handle !effect_free args + return false + end + if !isType(t) || has_typevars(t.parameters[1]) + if isconstantref(arg, sv) === false + return false + end + end + end + return true +end + +function _ieval_args(args, argtypes::Vector{Any}, sv::StaticVarInfo) + c = cell(length(args)) + for i = 1:length(argtypes) + t = argtypes[i] + if isType(t) && !has_typevars(t.parameters[1]) + c[i] = t.parameters[1] + else + c[i] = _ieval(isconstantref(args[i], sv)) + end + end + return c +end + +@pure function type_typeof(v::ANY) + if isa(v, Type) + return Type{v} + end + return typeof(v) +end + +function pure_eval_call(f, fargs, argtypes, sv, e) + if !isa(f, Function) # TODO: maybe replace with :call? + return false + end + if !isconstantargs(fargs, argtypes, sv) + return false + end + + fargs = _ieval_args(fargs, argtypes, sv) + tm = _topmod() + if isgeneric(f) + atype = Tuple{Any[type_typeof(a) for a in fargs]...} + meth = _methods(f, atype, 1) + if meth === false || length(meth) != 1 + return false + end + meth = meth[1]::SimpleVector + linfo = try + func_for_method(meth[3], atype, meth[2]) + catch + NF + end + if linfo === NF + return false + end + if !linfo.pure + typeinf(linfo, meth[1], meth[2], linfo) + if !linfo.pure + return false + end + end + elseif !isdefined(f, :code) || !f.code.pure + return false + end + + local v + try + v = f(fargs...) + catch + return false + end + if isa(e, Expr) + e.head = :inert # eval_annotate will turn this into a QuoteNode + e.args = Any[v] + end + return type_typeof(v) +end + + function abstract_call(f, fargs, argtypes::Vector{Any}, vtypes, sv::StaticVarInfo, e) + t = pure_eval_call(f, fargs, argtypes, sv, e) + t !== false && return t if is(f,_apply) && length(fargs)>1 af = isconstantfunc(fargs[2], sv) if !is(af,false) @@ -925,7 +1007,8 @@ function abstract_eval_call(e, vtypes, sv::StaticVarInfo) if !(Function <: ft) && _iisdefined(:call) call_func = _ieval(:call) if isa(call_func,Function) - return abstract_call(call_func, e.args, Any[ft,argtypes...], vtypes, sv, e) + unshift!(argtypes, ft) + return abstract_call(call_func, e.args, argtypes, vtypes, sv, e) end end return Any @@ -942,7 +1025,8 @@ end function abstract_eval(e::ANY, vtypes, sv::StaticVarInfo) if isa(e,QuoteNode) - return typeof((e::QuoteNode).value) + v = (e::QuoteNode).value + return type_typeof(v) elseif isa(e,TopNode) return abstract_eval_global(_topmod(), (e::TopNode).name) elseif isa(e,Symbol) @@ -1018,6 +1102,9 @@ function abstract_eval(e::ANY, vtypes, sv::StaticVarInfo) t = Function elseif is(e.head,:copyast) t = abstract_eval(e.args[1], vtypes, sv) + elseif is(e.head,:inert) + v = e.args[1] + return type_typeof(v) else t = Any end @@ -1712,6 +1799,9 @@ function typeinf_uncached(linfo::LambdaStaticData, atypes::ANY, sparams::SimpleV getfield_elim_pass(fulltree.args[3], sv) end linfo.inferred = true + body = Expr(:block) + body.args = fulltree.args[3].args::Array{Any,1} + linfo.pure = popmeta!(body, :pure)[1] fulltree = ccall(:jl_compress_ast, Any, (Any,Any), def, fulltree) end @@ -1778,6 +1868,8 @@ function eval_annotate(e::ANY, vtypes::ANY, sv::StaticVarInfo, decls, clo, undef return e #elseif is(head,:gotoifnot) || is(head,:return) # e.typ = Any + elseif is(head,:inert) + return QuoteNode(e.args[1]) elseif is(head,:(=)) # e.typ = Any s = e.args[1] @@ -1967,10 +2059,7 @@ function exprtype(x::ANY, sv::StaticVarInfo) return abstract_eval(x::Symbol, emptydict, sv) elseif isa(x,QuoteNode) v = (x::QuoteNode).value - if isa(v,Type) - return Type{v} - end - return typeof(v) + return type_typeof(v) elseif isa(x,Type) return Type{x} elseif isa(x,LambdaStaticData) @@ -2032,7 +2121,7 @@ function effect_free(e::ANY, sv, allow_volatile::Bool) allow_volatile && return true return isconst(e.mod, e.name) end - if isconstantfunc(e, sv) !== false + if isconstantref(e, sv) !== false return true end if isa(e,Expr) diff --git a/src/alloc.c b/src/alloc.c index ab453d5a8e264..402b8b209108b 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -361,6 +361,7 @@ jl_lambda_info_t *jl_new_lambda_info(jl_value_t *ast, jl_svec_t *sparams, jl_mod li->specFunctionID = 0; li->specTypes = NULL; li->inferred = 0; + li->pure = 0; li->inInference = 0; li->inCompile = 0; li->unspecialized = NULL; diff --git a/src/dump.c b/src/dump.c index e87b27aac7184..20c1571027d69 100644 --- a/src/dump.c +++ b/src/dump.c @@ -822,6 +822,7 @@ static void jl_serialize_value_(ios_t *s, jl_value_t *v) jl_serialize_value(s, (jl_value_t*)li->specTypes); jl_serialize_value(s, (jl_value_t*)li->specializations); write_int8(s, li->inferred); + write_int8(s, li->pure); jl_serialize_value(s, (jl_value_t*)li->file); write_int32(s, li->line); jl_serialize_value(s, (jl_value_t*)li->module); @@ -1366,6 +1367,7 @@ static jl_value_t *jl_deserialize_value_(ios_t *s, jl_value_t *vtag, jl_value_t li->specializations = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&li->specializations); if (li->specializations) jl_gc_wb(li, li->specializations); li->inferred = read_int8(s); + li->pure = read_int8(s); li->file = (jl_sym_t*)jl_deserialize_value(s, NULL); jl_gc_wb(li, li->file); li->line = read_int32(s); diff --git a/src/interpreter.c b/src/interpreter.c index df6da9788cefe..509cd6e50b319 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -493,6 +493,9 @@ static jl_value_t *eval(jl_value_t *e, jl_value_t **locals, size_t nl, size_t ng else if (ex->head == meta_sym) { return (jl_value_t*)jl_nothing; } + else if (ex->head == inert_sym) { + return args[0]; + } jl_errorf("unsupported or misplaced expression %s", ex->head->name); return (jl_value_t*)jl_nothing; } diff --git a/src/jltypes.c b/src/jltypes.c index 9c76d07c20414..9b4c3491233e5 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3420,7 +3420,7 @@ void jl_init_types(void) jl_lambda_info_type = jl_new_datatype(jl_symbol("LambdaStaticData"), jl_any_type, jl_emptysvec, - jl_svec(14, jl_symbol("ast"), jl_symbol("sparams"), + jl_svec(15, jl_symbol("ast"), jl_symbol("sparams"), jl_symbol("tfunc"), jl_symbol("name"), jl_symbol("roots"), /* jl_symbol("specTypes"), @@ -3430,15 +3430,16 @@ void jl_init_types(void) jl_symbol("module"), jl_symbol("def"), jl_symbol("capt"), jl_symbol("file"), jl_symbol("line"), - jl_symbol("inferred")), - jl_svec(14, jl_any_type, jl_simplevector_type, + jl_symbol("inferred"), + jl_symbol("pure")), + jl_svec(15, jl_any_type, jl_simplevector_type, jl_any_type, jl_sym_type, jl_any_type, jl_any_type, jl_any_type, jl_array_any_type, jl_module_type, jl_any_type, jl_any_type, jl_sym_type, jl_int32_type, - jl_bool_type), + jl_bool_type, jl_bool_type), 0, 1, 4); jl_box_type = diff --git a/src/julia.h b/src/julia.h index 43da2db6963c7..39b894d08ed55 100644 --- a/src/julia.h +++ b/src/julia.h @@ -203,6 +203,7 @@ typedef struct _jl_lambda_info_t { jl_sym_t *file; int32_t line; int8_t inferred; + int8_t pure; // hidden fields: // flag telling if inference is running on this function From 0cd98aa7b51273ba140e38ce2ea6913f3546b97f Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 10 Oct 2015 01:52:26 -0400 Subject: [PATCH 2/5] detect @pure in src, without requiring inference to have been run on a function --- src/alloc.c | 9 ++++++--- src/ast.c | 16 ++++++++++++++++ src/jltypes.c | 1 + src/julia.h | 2 +- src/julia_internal.h | 1 + 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index 402b8b209108b..40b970cb183de 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -100,7 +100,7 @@ jl_sym_t *compositetype_sym; jl_sym_t *type_goto_sym; jl_sym_t *global_sym; jl_sym_t *tuple_sym; jl_sym_t *dot_sym; jl_sym_t *newvar_sym; jl_sym_t *boundscheck_sym; jl_sym_t *copyast_sym; -jl_sym_t *fastmath_sym; +jl_sym_t *fastmath_sym; jl_sym_t *pure_sym; jl_sym_t *simdloop_sym; jl_sym_t *meta_sym; jl_sym_t *arrow_sym; jl_sym_t *inert_sym; jl_sym_t *vararg_sym; @@ -339,8 +339,12 @@ jl_lambda_info_t *jl_new_lambda_info(jl_value_t *ast, jl_svec_t *sparams, jl_mod li->ast = ast; li->file = null_sym; li->line = 0; + li->pure = 0; if (ast != NULL && jl_is_expr(ast)) { - jl_value_t *body1 = skip_meta(jl_lam_body((jl_expr_t*)ast)->args); + jl_array_t *body = jl_lam_body((jl_expr_t*)ast)->args; + if (has_meta(body, pure_sym)) + li->pure = 1; + jl_value_t *body1 = skip_meta(body); if (jl_is_linenode(body1)) { li->file = jl_linenode_file(body1); li->line = jl_linenode_line(body1); @@ -361,7 +365,6 @@ jl_lambda_info_t *jl_new_lambda_info(jl_value_t *ast, jl_svec_t *sparams, jl_mod li->specFunctionID = 0; li->specTypes = NULL; li->inferred = 0; - li->pure = 0; li->inInference = 0; li->inCompile = 0; li->unspecialized = NULL; diff --git a/src/ast.c b/src/ast.c index 8242bc3e17932..1051b503e07be 100644 --- a/src/ast.c +++ b/src/ast.c @@ -1008,6 +1008,22 @@ jl_value_t *skip_meta(jl_array_t *body) return body1; } +int has_meta(jl_array_t *body, jl_sym_t *sym) +{ + size_t i, l = jl_array_len(body); + for (i = 0; i < l; i++) { + jl_expr_t *stmt = (jl_expr_t*)jl_cellref(body, i); + if (jl_is_expr((jl_value_t*)stmt) && stmt->head == meta_sym) { + size_t i, l = jl_array_len(stmt->args); + for (i = 0; i < l; i++) + if (jl_cellref(stmt->args, i) == (jl_value_t*)sym) + return 1; + } + } + return 0; +} + + int jl_in_vinfo_array(jl_array_t *a, jl_sym_t *v) { size_t i, l=jl_array_len(a); diff --git a/src/jltypes.c b/src/jltypes.c index 9b4c3491233e5..04f618b84867b 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3573,6 +3573,7 @@ void jl_init_types(void) newvar_sym = jl_symbol("newvar"); copyast_sym = jl_symbol("copyast"); simdloop_sym = jl_symbol("simdloop"); + pure_sym = jl_symbol("pure"); meta_sym = jl_symbol("meta"); arrow_sym = jl_symbol("->"); dots_sym = jl_symbol("..."); diff --git a/src/julia.h b/src/julia.h index 39b894d08ed55..6b8863c684622 100644 --- a/src/julia.h +++ b/src/julia.h @@ -493,7 +493,7 @@ extern jl_sym_t *abstracttype_sym; extern jl_sym_t *bitstype_sym; extern jl_sym_t *compositetype_sym; extern jl_sym_t *type_goto_sym; extern jl_sym_t *global_sym; extern jl_sym_t *tuple_sym; extern jl_sym_t *boundscheck_sym; extern jl_sym_t *copyast_sym; -extern jl_sym_t *fastmath_sym; +extern jl_sym_t *fastmath_sym; extern jl_sym_t *pure_sym; extern jl_sym_t *simdloop_sym; extern jl_sym_t *meta_sym; extern jl_sym_t *arrow_sym; extern jl_sym_t *inert_sym; diff --git a/src/julia_internal.h b/src/julia_internal.h index ea14b50079e83..b878383ad7617 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -155,6 +155,7 @@ void jl_generate_fptr(jl_function_t *f); void jl_fptr_to_llvm(void *fptr, jl_lambda_info_t *lam, int specsig); jl_value_t* skip_meta(jl_array_t *body); +int has_meta(jl_array_t *body, jl_sym_t *sym); // backtraces #ifdef _OS_WINDOWS_ From c416e9fb92d32f1ac926d113cb931b14dc5a17ce Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 10 Oct 2015 01:07:02 -0400 Subject: [PATCH 3/5] add @pure annotation to promotion functions --- base/essentials.jl | 9 +++++++-- base/promotion.jl | 47 +++++++++++++++++++++++++--------------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/base/essentials.jl b/base/essentials.jl index a02585ad1d6b8..a43d5cf13e225 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -14,6 +14,9 @@ end macro _noinline_meta() Expr(:meta, :noinline) end +macro _pure_meta() + Expr(:meta, :pure) +end # constructors for Core types in boot.jl @@ -74,7 +77,8 @@ macro generated(f) end -@generated function tuple_type_head{T<:Tuple}(::Type{T}) +function tuple_type_head{T<:Tuple}(::Type{T}) + @_pure_meta T.parameters[1] end @@ -82,7 +86,8 @@ isvarargtype(t::ANY) = isa(t,DataType)&&is((t::DataType).name,Vararg.name) isvatuple(t::DataType) = (n = length(t.parameters); n > 0 && isvarargtype(t.parameters[n])) unwrapva(t::ANY) = isvarargtype(t) ? t.parameters[1] : t -@generated function tuple_type_tail{T<:Tuple}(::Type{T}) +function tuple_type_tail{T<:Tuple}(::Type{T}) + @_pure_meta if isvatuple(T) && length(T.parameters) == 1 return T end diff --git a/base/promotion.jl b/base/promotion.jl index 63e3b9a3d7606..92fe23d5a70ff 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -2,10 +2,11 @@ ## type join (closest common ancestor, or least upper bound) ## -typejoin() = Bottom -typejoin(t::ANY) = t -typejoin(t::ANY, ts...) = typejoin(t, typejoin(ts...)) +typejoin() = (@_pure_meta; Bottom) +typejoin(t::ANY) = (@_pure_meta; t) +typejoin(t::ANY, ts...) = (@_pure_meta; typejoin(t, typejoin(ts...))) function typejoin(a::ANY, b::ANY) + @_pure_meta if isa(a,TypeConstructor); a = a.body; end if isa(b,TypeConstructor); b = b.body; end if a <: b @@ -102,37 +103,39 @@ end ## promotion mechanism ## -promote_type() = Bottom -promote_type(T) = T -promote_type(T, S, U, V...) = promote_type(T, promote_type(S, U, V...)) +promote_type() = (@_pure_meta; Bottom) +promote_type(T) = (@_pure_meta; T) +promote_type(T, S, U, V...) = (@_pure_meta; promote_type(T, promote_type(S, U, V...))) -promote_type(::Type{Bottom}, ::Type{Bottom}) = Bottom -promote_type{T}(::Type{T}, ::Type{T}) = T -promote_type{T}(::Type{T}, ::Type{Bottom}) = T -promote_type{T}(::Type{Bottom}, ::Type{T}) = T +promote_type(::Type{Bottom}, ::Type{Bottom}) = (@_pure_meta; Bottom) +promote_type{T}(::Type{T}, ::Type{T}) = (@_pure_meta; T) +promote_type{T}(::Type{T}, ::Type{Bottom}) = (@_pure_meta; T) +promote_type{T}(::Type{Bottom}, ::Type{T}) = (@_pure_meta; T) # Try promote_rule in both orders. Typically only one is defined, # and there is a fallback returning Bottom below, so the common case is # promote_type(T, S) => # promote_result(T, S, result, Bottom) => # typejoin(result, Bottom) => result -promote_type{T,S}(::Type{T}, ::Type{S}) = +function promote_type{T,S}(::Type{T}, ::Type{S}) + @_pure_meta promote_result(T, S, promote_rule(T,S), promote_rule(S,T)) +end -promote_rule(T, S) = Bottom +promote_rule(T, S) = (@_pure_meta; Bottom) -promote_result(t,s,T,S) = promote_type(T,S) +promote_result(t,s,T,S) = (@_pure_meta; promote_type(T,S)) # If no promote_rule is defined, both directions give Bottom. In that # case use typejoin on the original types instead. -promote_result{T,S}(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) = typejoin(T, S) +promote_result{T,S}(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) = (@_pure_meta; typejoin(T, S)) promote() = () promote(x) = (x,) function promote{T,S}(x::T, y::S) (convert(promote_type(T,S),x), convert(promote_type(T,S),y)) end -promote_typeof(x) = typeof(x) -promote_typeof(x, xs...) = promote_type(typeof(x), promote_typeof(xs...)) +promote_typeof(x) = (@_pure_meta; typeof(x)) +promote_typeof(x, xs...) = (@_pure_meta; promote_type(typeof(x), promote_typeof(xs...))) function promote(x, y, z) (convert(promote_typeof(x,y,z), x), convert(promote_typeof(x,y,z), y), @@ -152,15 +155,17 @@ end # Otherwise, typejoin(T,S) is called (returning Number) so no conversion # happens, and +(promote(x,y)...) is called again, causing a stack # overflow. -promote_result{T<:Number,S<:Number}(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) = +function promote_result{T<:Number,S<:Number}(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) + @_pure_meta promote_to_super(T, S, typejoin(T,S)) +end # promote numeric types T and S to typejoin(T,S) if T<:S or S<:T # for example this makes promote_type(Integer,Real) == Real without # promoting arbitrary pairs of numeric types to Number. -promote_to_super{T<:Number }(::Type{T}, ::Type{T}, ::Type{T}) = T -promote_to_super{T<:Number,S<:Number}(::Type{T}, ::Type{S}, ::Type{T}) = T -promote_to_super{T<:Number,S<:Number}(::Type{T}, ::Type{S}, ::Type{S}) = S +promote_to_super{T<:Number }(::Type{T}, ::Type{T}, ::Type{T}) = (@_pure_meta; T) +promote_to_super{T<:Number,S<:Number}(::Type{T}, ::Type{S}, ::Type{T}) = (@_pure_meta; T) +promote_to_super{T<:Number,S<:Number}(::Type{T}, ::Type{S}, ::Type{S}) = (@_pure_meta; S) promote_to_super{T<:Number,S<:Number}(::Type{T}, ::Type{S}, ::Type) = error("no promotion exists for ", T, " and ", S) @@ -203,7 +208,7 @@ checked_mul(x::Integer, y::Integer) = checked_mul(promote(x,y)...) # as needed. For example, if you need to provide a custom result type # for the multiplication of two types, # promote_op{R<:MyType,S<:MyType}(::MulFun, ::Type{R}, ::Type{S}) = MyType{multype(R,S)} -promote_op{R,S}(::Any, ::Type{R}, ::Type{S}) = promote_type(R, S) +promote_op{R,S}(::Any, ::Type{R}, ::Type{S}) = (@_pure_meta; promote_type(R, S)) ## catch-alls to prevent infinite recursion when definitions are missing ## From 8ce7a02f6bed51a09600307f395d8c6887f0f104 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sat, 10 Oct 2015 01:50:37 -0400 Subject: [PATCH 4/5] mark some more key functions are @pure --- base/abstractarray.jl | 9 --------- base/iterator.jl | 3 ++- base/reflection.jl | 27 ++++++++++++++------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 3fde6b72ed129..7d6eacaa640b8 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -117,15 +117,6 @@ linearindexing(A::AbstractArray, B::AbstractArray...) = linearindexing(linearind linearindexing(::LinearFast, ::LinearFast) = LinearFast() linearindexing(::LinearIndexing, ::LinearIndexing) = LinearSlow() -# The real @inline macro is not available this early in the bootstrap, so this -# internal macro splices the meta Expr directly into the function body. -macro _inline_meta() - Expr(:meta, :inline) -end -macro _noinline_meta() - Expr(:meta, :noinline) -end - ## Bounds checking ## @generated function trailingsize{T,N,n}(A::AbstractArray{T,N}, ::Type{Val{n}}) n > N && return 1 diff --git a/base/iterator.jl b/base/iterator.jl index d3f76c1e83f0c..a040728704ffc 100644 --- a/base/iterator.jl +++ b/base/iterator.jl @@ -45,7 +45,8 @@ immutable Zip{I, Z<:AbstractZipIterator} <: AbstractZipIterator end zip(a, b, c...) = Zip(a, zip(b, c...)) length(z::Zip) = min(length(z.a), length(z.z)) -@generated function tuple_type_cons{S,T<:Tuple}(::Type{S}, ::Type{T}) +function tuple_type_cons{S,T<:Tuple}(::Type{S}, ::Type{T}) + @_pure_meta Tuple{S, T.parameters...} end eltype{I,Z}(::Type{Zip{I,Z}}) = tuple_type_cons(eltype(I), eltype(Z)) diff --git a/base/reflection.jl b/base/reflection.jl index feda3d695bdf3..9d152edaafc53 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -71,16 +71,16 @@ isconst(m::Module, s::Symbol) = object_id(x::ANY) = ccall(:jl_object_id, UInt, (Any,), x) # type predicates -isimmutable(x::ANY) = (isa(x,Tuple) || !typeof(x).mutable) -isstructtype(t::DataType) = nfields(t) != 0 || (t.size==0 && !t.abstract) -isstructtype(x) = false -isbits(t::DataType) = !t.mutable & t.pointerfree & isleaftype(t) -isbits(t::Type) = false -isbits(x) = isbits(typeof(x)) -isleaftype(t::ANY) = ccall(:jl_is_leaf_type, Int32, (Any,), t) != 0 +isimmutable(x::ANY) = (@_pure_meta; (isa(x,Tuple) || !typeof(x).mutable)) +isstructtype(t::DataType) = (@_pure_meta; nfields(t) != 0 || (t.size==0 && !t.abstract)) +isstructtype(x) = (@_pure_meta; false) +isbits(t::DataType) = (@_pure_meta; !t.mutable & t.pointerfree & isleaftype(t)) +isbits(t::Type) = (@_pure_meta; false) +isbits(x) = (@_pure_meta; isbits(typeof(x))) +isleaftype(t::ANY) = (@_pure_meta; ccall(:jl_is_leaf_type, Int32, (Any,), t) != 0) -typeintersect(a::ANY,b::ANY) = ccall(:jl_type_intersection, Any, (Any,Any), a, b) -typeseq(a::ANY,b::ANY) = a<:b && b<:a +typeintersect(a::ANY,b::ANY) = (@_pure_meta; ccall(:jl_type_intersection, Any, (Any,Any), a, b)) +typeseq(a::ANY,b::ANY) = (@_pure_meta; a<:b && b<:a) function fieldoffsets(x::DataType) offsets = Array(Int, nfields(x)) @@ -88,8 +88,8 @@ function fieldoffsets(x::DataType) offsets end -type_alignment(x::DataType) = ccall(:jl_get_alignment,Csize_t,(Any,),x) -field_offset(x::DataType,idx) = ccall(:jl_get_field_offset,Csize_t,(Any,Int32),x,idx) +type_alignment(x::DataType) = (@_pure_meta; ccall(:jl_get_alignment,Csize_t,(Any,),x)) +field_offset(x::DataType,idx) = (@_pure_meta; ccall(:jl_get_field_offset,Csize_t,(Any,Int32),x,idx)) # return all instances, for types that can be enumerated function instances end @@ -114,11 +114,12 @@ subtypes(m::Module, x::DataType) = sort(collect(_subtypes(m, x)), by=string) subtypes(x::DataType) = subtypes(Main, x) # function reflection -isgeneric(f::ANY) = (isa(f,Function) && isa(f.env,MethodTable)) +isgeneric(f::ANY) = (@_pure_meta; (isa(f,Function) && isa(f.env,MethodTable))) function_name(f::Function) = isgeneric(f) ? f.env.name : (:anonymous) function to_tuple_type(t::ANY) + @_pure_meta if isa(t,Tuple) || isa(t,AbstractArray) || isa(t,SimpleVector) t = Tuple{t...} end @@ -132,7 +133,7 @@ function to_tuple_type(t::ANY) t end -tt_cons(t::ANY, tup::ANY) = Tuple{t, (isa(tup, Type) ? tup.parameters : tup)...} +tt_cons(t::ANY, tup::ANY) = (@_pure_meta; Tuple{t, (isa(tup, Type) ? tup.parameters : tup)...}) code_lowered(f, t::ANY) = map(m->uncompressed_ast(m.func.code), methods(f, t)) function methods(f::Function,t::ANY) From b7278d1833f06dd64d491f61913b54b91b39e1f3 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 11 Oct 2015 23:03:53 -0400 Subject: [PATCH 5/5] add support for calling `@pure` functions with args that are potentially not effect free (some of the linalg code expects this) --- base/inference.jl | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/base/inference.jl b/base/inference.jl index 49a1115751781..04558cf4d443d 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -820,9 +820,6 @@ function isconstantargs(args, argtypes::Vector{Any}, sv::StaticVarInfo) for i = 1:length(args) arg = args[i] t = argtypes[i] - if !effect_free(arg, sv, false) # TODO: handle !effect_free args - return false - end if !isType(t) || has_typevars(t.parameters[1]) if isconstantref(arg, sv) === false return false @@ -860,10 +857,10 @@ function pure_eval_call(f, fargs, argtypes, sv, e) return false end - fargs = _ieval_args(fargs, argtypes, sv) + args = _ieval_args(fargs, argtypes, sv) tm = _topmod() if isgeneric(f) - atype = Tuple{Any[type_typeof(a) for a in fargs]...} + atype = Tuple{Any[type_typeof(a) for a in args]...} meth = _methods(f, atype, 1) if meth === false || length(meth) != 1 return false @@ -889,13 +886,25 @@ function pure_eval_call(f, fargs, argtypes, sv, e) local v try - v = f(fargs...) + v = f(args...) catch return false end - if isa(e, Expr) - e.head = :inert # eval_annotate will turn this into a QuoteNode - e.args = Any[v] + if isa(e, Expr) # replace Expr with a constant + stmts = Any[] # check if any arguments aren't effect_free and need to be kept around + for i = 1:length(fargs) + arg = fargs[i] + if !effect_free(arg, sv, false) + push!(stmts, arg) + end + end + if isempty(stmts) + e.head = :inert # eval_annotate will turn this into a QuoteNode + e.args = Any[v] + else + e.head = :call # should get cleaned up by tuple elimination + e.args = Any[top_getfield, Expr(:call, top_tuple, stmts..., v), length(stmts) + 1] + end end return type_typeof(v) end