Skip to content

Commit

Permalink
replace ANY with @nospecialize annotation. part of #11339
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed Jul 4, 2017
1 parent 4b345c1 commit 24dec72
Show file tree
Hide file tree
Showing 21 changed files with 115 additions and 57 deletions.
4 changes: 4 additions & 0 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion base/distributed/remotecall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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...)

Expand Down
8 changes: 0 additions & 8 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ export
@simd,
@inline,
@noinline,
@nospecialize,
@polly,

@assert,
Expand Down
8 changes: 4 additions & 4 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions doc/src/devdocs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions doc/src/stdlib/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ Base.esc
Base.@inbounds
Base.@inline
Base.@noinline
Base.@nospecialize
Base.gensym
Base.@gensym
Base.@polly
Expand Down
1 change: 0 additions & 1 deletion doc/src/stdlib/constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <julia_flisp.boot.inc>
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions src/ast.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 5 additions & 15 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
/*
Expand Down
12 changes: 5 additions & 7 deletions src/jltypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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",
Expand All @@ -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,
Expand All @@ -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),
Expand Down
15 changes: 13 additions & 2 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) "\""))))))

Expand Down Expand Up @@ -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)))))
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 24dec72

Please sign in to comment.