diff --git a/base/boot.jl b/base/boot.jl index 8066388cf30f2..03156d78c78b9 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -196,6 +196,10 @@ macro _noinline_meta() Expr(:meta, :noinline) end +macro nospecialize(x) + Expr(:meta, :nospecialize, x) +end + struct BoundsError <: Exception a::Any i::Any diff --git a/base/distributed/remotecall.jl b/base/distributed/remotecall.jl index d621f9ada0654..262828171283b 100644 --- a/base/distributed/remotecall.jl +++ b/base/distributed/remotecall.jl @@ -484,7 +484,7 @@ Waits and fetches a value from `x` depending on the type of `x`: Does not remove the item fetched. """ -fetch(x::ANY) = x +fetch(@nospecialize x) = x isready(rv::RemoteValue, args...) = isready(rv.c, args...) diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index c99b9f747cb40..e675042c4373f 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -689,14 +689,6 @@ The singleton instance of type `Void`, used by convention when there is no value """ nothing -""" - ANY - -Equivalent to `Any` for dispatch purposes, but signals the compiler to skip code -generation specialization for that field. -""" -ANY - """ Core.TypeofBottom diff --git a/base/essentials.jl b/base/essentials.jl index 6f3b24e784d99..1ccfa58479d8d 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -17,6 +17,29 @@ end macro _noinline_meta() Expr(:meta, :noinline) end +""" + @nospecialize + +Applied to a function argument name, hints to the compiler that the method +should not be specialized for different types of that argument. +This is only a hint for avoiding excess code generation. +Can be applied to an argument within a formal argument list, or in the +function body: + +```julia +function example_function(@nospecialize x) + ... +end + +function example_function(x, y, z) + @nospecialize x y + ... +end +``` +""" +macro nospecialize(var, vars...) + Expr(:meta, :nospecialize, var, vars...) +end macro _pure_meta() Expr(:meta, :pure) end diff --git a/base/exports.jl b/base/exports.jl index 34e266a0ee002..4d9c0cf1e1b00 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1285,6 +1285,7 @@ export @simd, @inline, @noinline, + @nospecialize, @polly, @assert, diff --git a/base/reflection.jl b/base/reflection.jl index 83b6924e00c88..18db8ce6751b9 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -837,7 +837,7 @@ Returns the method of `f` (a `Method` object) that would be called for arguments If `types` is an abstract type, then the method that would be called by `invoke` is returned. """ -function which(f::ANY, t::ANY) +function which(@nospecialize(f), @nospecialize(t)) if isa(f, Core.Builtin) throw(ArgumentError("argument is not a generic function")) end @@ -904,9 +904,9 @@ end Returns a tuple `(filename,line)` giving the location of a generic `Function` definition. """ -functionloc(f::ANY, types::ANY) = functionloc(which(f,types)) +functionloc(@nospecialize(f), @nospecialize(types)) = functionloc(which(f,types)) -function functionloc(f::ANY) +function functionloc(@nospecialize(f)) mt = methods(f) if isempty(mt) if isa(f, Function) @@ -934,7 +934,7 @@ function_module(f::Function) = datatype_module(typeof(f)) Determine the module containing a given definition of a generic function. """ -function function_module(f::ANY, types::ANY) +function function_module(@nospecialize(f), @nospecialize(types)) m = methods(f, types) if isempty(m) error("no matching methods") diff --git a/doc/src/devdocs/functions.md b/doc/src/devdocs/functions.md index 70c509ec341d2..9f283dc8d10f0 100644 --- a/doc/src/devdocs/functions.md +++ b/doc/src/devdocs/functions.md @@ -223,8 +223,8 @@ Performance-critical higher-order functions like `map` certainly call their argu and so will still be specialized as expected. This optimization is implemented by recording which arguments are called during the `analyze-variables` pass in the front end. When `cache_method` sees an argument in the `Function` type hierarchy passed to a slot declared as `Any` or `Function`, -it pretends the slot was declared as `ANY` (the "don't specialize" hint). This heuristic seems -to be extremely effective in practice. +it behaves as if the `@nospecialize` annotation were applied. This heuristic seems to be extremely +effective in practice. The next issue concerns the structure of method cache hash tables. Empirical studies show that the vast majority of dynamically-dispatched calls involve one or two arguments. In turn, many diff --git a/doc/src/stdlib/base.md b/doc/src/stdlib/base.md index d17eb625344be..3db7e3b98631e 100644 --- a/doc/src/stdlib/base.md +++ b/doc/src/stdlib/base.md @@ -138,6 +138,7 @@ Base.esc Base.@inbounds Base.@inline Base.@noinline +Base.@nospecialize Base.gensym Base.@gensym Base.@polly diff --git a/doc/src/stdlib/constants.md b/doc/src/stdlib/constants.md index f5174fddc42d4..e88211b354038 100644 --- a/doc/src/stdlib/constants.md +++ b/doc/src/stdlib/constants.md @@ -8,7 +8,6 @@ Base.C_NULL Base.VERSION Base.LOAD_PATH Base.JULIA_HOME -Core.ANY Base.Sys.CPU_CORES Base.Sys.WORD_SIZE Base.Sys.KERNEL diff --git a/src/ast.c b/src/ast.c index aa1e5f3eee975..63f911af4060e 100644 --- a/src/ast.c +++ b/src/ast.c @@ -56,7 +56,7 @@ jl_sym_t *inert_sym; jl_sym_t *vararg_sym; jl_sym_t *unused_sym; jl_sym_t *static_parameter_sym; jl_sym_t *polly_sym; jl_sym_t *inline_sym; jl_sym_t *propagate_inbounds_sym; -jl_sym_t *isdefined_sym; +jl_sym_t *isdefined_sym; jl_sym_t *nospecialize_sym; static uint8_t flisp_system_image[] = { #include @@ -433,6 +433,7 @@ void jl_init_frontend(void) inline_sym = jl_symbol("inline"); propagate_inbounds_sym = jl_symbol("propagate_inbounds"); isdefined_sym = jl_symbol("isdefined"); + nospecialize_sym = jl_symbol("nospecialize"); } JL_DLLEXPORT void jl_lisp_prompt(void) diff --git a/src/ast.scm b/src/ast.scm index 7502ebb897ab6..1f33b6ad94b3f 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -152,6 +152,10 @@ (if (not (symbol? (cadr v))) (bad-formal-argument (cadr v))) (decl-var v)) + ((meta) ;; allow certain per-argument annotations + (if (and (length= v 3) (eq? (cadr v) 'nospecialize)) + (arg-name (caddr v)) + (bad-formal-argument v))) (else (bad-formal-argument v)))))) (define (arg-type v) @@ -167,6 +171,10 @@ (if (not (symbol? (cadr v))) (bad-formal-argument (cadr v))) (decl-type v)) + ((meta) ;; allow certain per-argument annotations + (if (and (length= v 3) (eq? (cadr v) 'nospecialize)) + (arg-type (caddr v)) + (bad-formal-argument v))) (else (bad-formal-argument v)))))) ;; convert a lambda list into a list of just symbols @@ -310,6 +318,9 @@ (define (kwarg? e) (and (pair? e) (eq? (car e) 'kw))) +(define (nospecialize-meta? e) + (and (length> e 2) (eq? (car e) 'meta) (eq? (cadr e) 'nospecialize))) + ;; flatten nested expressions with the given head ;; (op (op a b) c) => (op a b c) (define (flatten-ex head e) diff --git a/src/gf.c b/src/gf.c index 1247383b24c47..724673226708f 100644 --- a/src/gf.c +++ b/src/gf.c @@ -611,8 +611,8 @@ static void jl_cacheable_sig( int notcalled_func = (i > 0 && i <= 8 && !(definition->called & (1 << (i - 1))) && jl_subtype(elt, (jl_value_t*)jl_function_type)); - if (decl_i == jl_ANY_flag) { - // don't specialize on slots marked ANY + if (i > 0 && i <= 8 && (definition->nospec & (1 << (i - 1))) && + decl_i == (jl_value_t*)jl_any_type) { // TODO: nospecialize with other types if (!*newparams) *newparams = jl_svec_copy(type->parameters); jl_svecset(*newparams, i, (jl_value_t*)jl_any_type); *need_guard_entries = 1; @@ -714,9 +714,9 @@ JL_DLLEXPORT int jl_is_cacheable_sig( continue; if (jl_is_kind(elt)) // kind slots always need guard entries (checking for subtypes of Type) continue; - if (decl_i == jl_ANY_flag) { - // don't specialize on slots marked ANY - if (elt != (jl_value_t*)jl_any_type && elt != jl_ANY_flag) + if (i > 0 && i <= 8 && (definition->nospec & (1 << (i - 1))) && + decl_i == (jl_value_t*)jl_any_type) { // TODO: nospecialize with other types + if (elt != (jl_value_t*)jl_any_type) return 0; continue; } @@ -2258,16 +2258,6 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio break; } } - // don't analyze slots declared with ANY - // TODO - /* - l = jl_nparams(ml->sig); - size_t m = jl_nparams(ti); - for(i=0; i < l && i < m; i++) { - if (jl_tparam(ml->sig, i) == jl_ANY_flag) - jl_tupleset(ti, i, jl_any_type); - } - */ } if (!skip) { /* diff --git a/src/jltypes.c b/src/jltypes.c index b74b39da06331..3aad328658b16 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -146,7 +146,6 @@ static int typeenv_has(jl_typeenv_t *env, jl_tvar_t *v) static int has_free_typevars(jl_value_t *v, jl_typeenv_t *env) { if (jl_typeis(v, jl_tvar_type)) { - if (v == jl_ANY_flag) return 0; return !typeenv_has(env, (jl_tvar_t*)v); } if (jl_is_uniontype(v)) @@ -181,7 +180,6 @@ JL_DLLEXPORT int jl_has_free_typevars(jl_value_t *v) static void find_free_typevars(jl_value_t *v, jl_typeenv_t *env, jl_array_t *out) { if (jl_typeis(v, jl_tvar_type)) { - if (v == jl_ANY_flag) return; if (!typeenv_has(env, (jl_tvar_t*)v)) jl_array_ptr_1d_push(out, v); } @@ -238,7 +236,7 @@ static int jl_has_bound_typevars(jl_value_t *v, jl_typeenv_t *env) return ans; } if (jl_is_datatype(v)) { - if (!((jl_datatype_t*)v)->hasfreetypevars && !(env && env->var == (jl_tvar_t*)jl_ANY_flag)) + if (!((jl_datatype_t*)v)->hasfreetypevars) return 0; size_t i; for (i=0; i < jl_nparams(v); i++) { @@ -669,8 +667,6 @@ static int is_cacheable(jl_datatype_t *type) assert(jl_is_datatype(type)); jl_svec_t *t = type->parameters; if (jl_svec_len(t) == 0) return 0; - if (jl_has_typevar((jl_value_t*)type, (jl_tvar_t*)jl_ANY_flag)) - return 0; // cache abstract types with no free type vars if (jl_is_abstracttype(type)) return !jl_has_free_typevars((jl_value_t*)type); @@ -1939,7 +1935,7 @@ void jl_init_types(void) jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(19, + jl_perm_symsvec(20, "name", "module", "file", @@ -1956,10 +1952,11 @@ void jl_init_types(void) "invokes", "nargs", "called", + "nospec", "isva", "isstaged", "pure"), - jl_svec(19, + jl_svec(20, jl_sym_type, jl_module_type, jl_sym_type, @@ -1976,6 +1973,7 @@ void jl_init_types(void) jl_any_type, jl_int32_type, jl_int32_type, + jl_int32_type, jl_bool_type, jl_bool_type, jl_bool_type), diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 03821caa2abba..185b45f2294a5 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1026,6 +1026,14 @@ (string "function Base.broadcast(::typeof(" (deparse op_) "), ...)"))) op_)) (name (if op '(|.| Base (inert broadcast)) name)) + (annotations (map (lambda (a) `(meta nospecialize ,(arg-name a))) + (filter nospecialize-meta? argl))) + (body (if (null? annotations) + (caddr e) + (insert-after-meta (caddr e) annotations))) + (argl (map (lambda (a) + (if (nospecialize-meta? a) (caddr a) a)) + argl)) (argl (if op (cons `(|::| (call (core Typeof) ,op)) argl) argl)) (sparams (map analyze-typevar (cond (has-sp (cddr head)) (where where) @@ -1046,7 +1054,7 @@ (name (if (or (decl? name) (and (pair? name) (eq? (car name) 'curly))) #f name))) (expand-forms - (method-def-expr name sparams argl (caddr e) isstaged rett)))) + (method-def-expr name sparams argl body isstaged rett)))) (else (error (string "invalid assignment location \"" (deparse name) "\"")))))) @@ -1170,7 +1178,7 @@ (|::| __module__ (core Module)) ,@(map (lambda (v) (if (symbol? v) - `(|::| ,v (core ANY)) + `(|::| ,v (core ANY)) ;; TODO: ANY deprecation v)) anames)) ,@(cddr e))))) @@ -3780,6 +3788,9 @@ f(x) = yt(x) ((and (pair? e) (eq? (car e) 'outerref)) (let ((idx (get sp-table (cadr e) #f))) (if idx `(static_parameter ,idx) (cadr e)))) + ((and (length> e 2) (eq? (car e) 'meta) (eq? (cadr e) 'nospecialize)) + ;; convert nospecialize vars to slot numbers + `(meta nospecialize ,@(map renumber-slots (cddr e)))) ((or (atom? e) (quoted? e)) e) ((ssavalue? e) (let ((idx (or (get ssavalue-table (cadr e) #f) diff --git a/src/julia.h b/src/julia.h index 0b433057e5e01..93a7cf6711092 100644 --- a/src/julia.h +++ b/src/julia.h @@ -255,6 +255,7 @@ typedef struct _jl_method_t { int32_t nargs; int32_t called; // bit flags: whether each of the first 8 arguments is called + int32_t nospec; // bit flags: which arguments should not be specialized uint8_t isva; uint8_t isstaged; uint8_t pure; diff --git a/src/julia_internal.h b/src/julia_internal.h index c9ee0d4dc1822..d6d3c9eccf4ab 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1001,7 +1001,7 @@ extern jl_sym_t *meta_sym; extern jl_sym_t *list_sym; extern jl_sym_t *inert_sym; extern jl_sym_t *static_parameter_sym; extern jl_sym_t *polly_sym; extern jl_sym_t *inline_sym; extern jl_sym_t *propagate_inbounds_sym; -extern jl_sym_t *isdefined_sym; +extern jl_sym_t *isdefined_sym; extern jl_sym_t *nospecialize_sym; void jl_register_fptrs(uint64_t sysimage_base, const char *base, const int32_t *offsets, jl_method_instance_t **linfos, size_t n); diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 8f2ad5784bb87..59f020cc21852 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -192,6 +192,10 @@ (case (car v) ((... kw |::|) (try-arg-name (cadr v))) ((escape) (list v)) + ((meta) ;; allow certain per-argument annotations + (if (and (length= v 3) (eq? (cadr v) 'nospecialize)) + (try-arg-name (caddr v)) + '())) (else '()))))) ;; get names from a formal argument list, specifying whether to include escaped ones diff --git a/src/method.c b/src/method.c index 630d3d60d2072..43fda3660dece 100644 --- a/src/method.c +++ b/src/method.c @@ -434,6 +434,15 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) set_lineno = 1; } } + else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == meta_sym && + jl_expr_nargs(st) > 1 && jl_exprarg(st,0) == (jl_value_t*)nospecialize_sym) { + for(size_t j=1; j < jl_expr_nargs(st); j++) { + jl_value_t *aj = jl_exprarg(st, j); + if (jl_is_slot(aj)) + m->nospec |= (1 << (jl_slot_number(aj) - 2)); + } + st = jl_nothing; + } else { st = jl_resolve_globals(st, m->module, sparam_vars); } @@ -465,6 +474,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->file = empty_sym; m->line = 0; m->called = 0xff; + m->nospec = 0; m->invokes.unknown = NULL; m->isstaged = 0; m->isva = 0; @@ -642,6 +652,16 @@ JL_DLLEXPORT void jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt; jl_sym_t *name; jl_method_t *m = NULL; + size_t i, na = jl_svec_len(atypes); + int32_t nospec = 0; + for(i=1; i < na; i++) { + jl_value_t *ti = jl_svecref(atypes, i); + if (ti == jl_ANY_flag || + (jl_is_vararg_type(ti) && jl_tparam0(jl_unwrap_unionall(ti)) == jl_ANY_flag)) { + nospec |= (1 << (i - 1)); + jl_svecset(atypes, i, jl_substitute_var(ti, (jl_tvar_t*)jl_ANY_flag, (jl_value_t*)jl_any_type)); + } + } jl_value_t *argtype = (jl_value_t*)jl_apply_tuple_type(atypes); JL_GC_PUSH3(&f, &m, &argtype); @@ -675,6 +695,7 @@ JL_DLLEXPORT void jl_method_def(jl_svec_t *argdata, } m = jl_new_method(f, name, module, (jl_tupletype_t*)argtype, nargs, isva, tvars, isstaged == jl_true); + m->nospec |= nospec; if (jl_has_free_typevars(argtype)) { jl_exceptionf(jl_argumenterror_type, @@ -686,7 +707,6 @@ JL_DLLEXPORT void jl_method_def(jl_svec_t *argdata, jl_check_static_parameter_conflicts(m, f, tvars); - size_t i, na = jl_svec_len(atypes); for (i = 0; i < na; i++) { jl_value_t *elt = jl_svecref(atypes, i); if (!jl_is_type(elt) && !jl_is_typevar(elt)) { diff --git a/src/subtype.c b/src/subtype.c index b0a4a81e1705b..c8645f3bf69c5 100644 --- a/src/subtype.c +++ b/src/subtype.c @@ -790,8 +790,6 @@ static int forall_exists_equal(jl_value_t *x, jl_value_t *y, jl_stenv_t *e); // diagonal rule (record_var_occurrence). static int subtype(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) { - if (x == jl_ANY_flag) x = (jl_value_t*)jl_any_type; - if (y == jl_ANY_flag) y = (jl_value_t*)jl_any_type; if (jl_is_uniontype(x)) { if (x == y) return 1; x = pick_union_element(x, e, 0); @@ -1826,8 +1824,6 @@ static jl_value_t *intersect_type_type(jl_value_t *x, jl_value_t *y, jl_stenv_t static jl_value_t *intersect(jl_value_t *x, jl_value_t *y, jl_stenv_t *e, int param) { if (x == y) return y; - if (x == jl_ANY_flag) x = (jl_value_t*)jl_any_type; - if (y == jl_ANY_flag) y = (jl_value_t*)jl_any_type; if (jl_is_typevar(x)) { if (jl_is_typevar(y)) { jl_varbinding_t *xx = lookup(e, (jl_tvar_t*)x); @@ -2219,10 +2215,6 @@ JL_DLLEXPORT jl_svec_t *jl_env_from_type_intersection(jl_value_t *a, jl_value_t static int eq_msp(jl_value_t *a, jl_value_t *b, jl_typeenv_t *env) { - // equate ANY and Any for specificity purposes, #16153 - if ((a == (jl_value_t*)jl_any_type && b == jl_ANY_flag) || - (b == (jl_value_t*)jl_any_type && a == jl_ANY_flag)) - return 1; if (!(jl_is_type(a) || jl_is_typevar(a)) || !(jl_is_type(b) || jl_is_typevar(b))) return jl_egal(a, b); @@ -2508,8 +2500,8 @@ static int type_morespecific_(jl_value_t *a, jl_value_t *b, int invariant, jl_ty } if (!invariant) { - if ((jl_datatype_t*)a == jl_any_type || a == jl_ANY_flag) return 0; - if ((jl_datatype_t*)b == jl_any_type || b == jl_ANY_flag) return 1; + if ((jl_datatype_t*)a == jl_any_type) return 0; + if ((jl_datatype_t*)b == jl_any_type) return 1; } if (jl_is_datatype(a) && jl_is_datatype(b)) { diff --git a/src/typemap.c b/src/typemap.c index d85a0fa8b4787..70cc0673d4e4e 100644 --- a/src/typemap.c +++ b/src/typemap.c @@ -19,7 +19,7 @@ extern "C" { // compute whether the specificity of this type is equivalent to Any in the sort order static int jl_is_any(jl_value_t *t1) { - return (t1 == (jl_value_t*)jl_any_type || t1 == jl_ANY_flag || + return (t1 == (jl_value_t*)jl_any_type || (jl_is_typevar(t1) && ((jl_tvar_t*)t1)->ub == (jl_value_t*)jl_any_type)); } @@ -69,7 +69,7 @@ static int sig_match_by_type_simple(jl_value_t **types, size_t n, jl_tupletype_t return 0; } } - else if (decl == (jl_value_t*)jl_any_type || decl == jl_ANY_flag) { + else if (decl == (jl_value_t*)jl_any_type) { } else { if (jl_is_type_type(a)) // decl is not Type, because it would be caught above @@ -122,8 +122,7 @@ static inline int sig_match_simple(jl_value_t **args, size_t n, jl_value_t **sig for (i = 0; i < lensig; i++) { jl_value_t *decl = sig[i]; jl_value_t *a = args[i]; - if (decl == (jl_value_t*)jl_any_type || decl == jl_ANY_flag || - ((jl_value_t*)jl_typeof(a) == decl)) { + if (decl == (jl_value_t*)jl_any_type || ((jl_value_t*)jl_typeof(a) == decl)) { /* we are only matching concrete types here, and those types are hash-consed, so pointer comparison should work. diff --git a/test/meta.jl b/test/meta.jl index 6e12549ab10ee..7faa5449f3be0 100644 --- a/test/meta.jl +++ b/test/meta.jl @@ -144,3 +144,14 @@ baremodule B end @test B.x == 3 @test B.M.x == 4 + +# specialization annotations +function _nospec_some_args(@nospecialize(x), y, @nospecialize z::Int) +end +@test first(methods(_nospec_some_args)).nospec == 5 +@test first(methods(_nospec_some_args)).sig == Tuple{typeof(_nospec_some_args),Any,Any,Int} +function _nospec_some_args2(x, y, z) + @nospecialize x y + return 0 +end +@test first(methods(_nospec_some_args2)).nospec == 3