Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: replace ANY with @nospecialize annotation #22666

Merged
merged 2 commits into from
Jul 14, 2017
Merged

RFC: replace ANY with @nospecialize annotation #22666

merged 2 commits into from
Jul 14, 2017

Conversation

JeffBezanson
Copy link
Sponsor Member

@JeffBezanson JeffBezanson commented Jul 3, 2017

Part of #11339.

This provides a much nicer and more general approach to specialization annotations, that stays out of the way of the type system.

TODO:

  • NEWS and doc updates
  • Add deprecation message
  • Replace uses of ANY in Base

@JeffBezanson JeffBezanson added the kind:deprecation This change introduces or involves a deprecation label Jul 3, 2017
@@ -1285,6 +1285,7 @@ export
@simd,
@inline,
@noinline,
@nospecialize,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add to stdlib doc index

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove from exports? Or maybe export from Base.Meta? Seems special-purpose enough that it doesn't need to be in the default namespace.

test/meta.jl Outdated
@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it redundant here on the z arg that has a concrete type constraint?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much; it will just have no effect in that case.

@ararslan ararslan added the domain:types and dispatch Types, subtyping and method dispatch label Jul 3, 2017
@iamed2
Copy link
Contributor

iamed2 commented Jul 3, 2017

Is there any functional difference between this and ANY?

@JeffBezanson
Copy link
Sponsor Member Author

No. But it will allow additional functionality in the future, such as @nospecialize x::AbstractArray, where the type and specialization are de-coupled.

src/julia.h Outdated
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how many arguments can this use before it breaks down? will there be a sane error or warning with 32 or more arguments?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a warning.

@andyferris
Copy link
Member

Very nice - I like this a lot!

src/method.c Outdated
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)) {
jl_depwarn("`x::ANY` is deprecated, use `@nospecialize(x)` instead.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add note in deprecated.jl

@@ -346,7 +346,7 @@ end
let f17314 = x -> x < 0 ? false : x
@test eltype(broadcast(f17314, 1:3)) === Int
@test eltype(broadcast(f17314, -1:1)) === Integer
@test eltype(broadcast(f17314, Int[])) === Union{Bool,Int}
@test eltype(broadcast(f17314, Int[])) == Union{Bool,Int}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this a bug or?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bug in the test. The type might be Union{Bool,Int} or Union{Int,Bool}, which have different structures that can be distinguished by ===. The show method for unions sorts the types though, obscuring this. #22664 is about normalizing union types so this doesn't come up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and changing ANY to nospecialize triggers a different result more often somehow?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that seems to be the case. I'm not sure it's worth the time to track down why.

@@ -1519,6 +1519,8 @@ function replace(s::AbstractString, pat, f, n::Integer)
end
end

# ::ANY is deprecated in src/method.c
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and jl_ANY_flag should be removed everywhere else in src, right?

@@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should these wait until the deprecation is removed?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified the method definition code to remove ANY from signatures before the type system sees them, so this should be ok. Using ANY outside a method signature is not allowed.

@JeffBezanson JeffBezanson force-pushed the jb/ANY branch 2 times, most recently from 43643d2 to bcbfefb Compare July 8, 2017 18:11
@tkelman
Copy link
Contributor

tkelman commented Jul 9, 2017

@nanosoldier runbenchmarks(ALL, vs=":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels

@JeffBezanson JeffBezanson force-pushed the jb/ANY branch 2 times, most recently from b481326 to fc8d192 Compare July 10, 2017 21:54
@timholy
Copy link
Sponsor Member

timholy commented Jul 12, 2017

FYI there are a couple new ANYs in boot.jl.

Copy link
Sponsor Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, just minor comments to fix.

base/boot.jl Outdated
ccall(:jl_toplevel_eval_in, Any, (Any, Any),
Core, quote
Expr(args...) = ($(_expr(:meta,:nospecialize,:args)); _expr(args...))
end)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be simpler to define @nospecialize as _expr(:meta, :nospecialize, x), then I think we can avoid the need to unroll this as much (since it can just use the macro).

@@ -17,6 +17,38 @@ end
macro _noinline_meta()
Expr(:meta, :noinline)
end
"""
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put empty newlines between functions

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.
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we want to encourage the latter form, but it should be mentioned that it should be the first statement (and must be a statement), or we might not scan it.

@@ -1285,6 +1285,7 @@ export
@simd,
@inline,
@noinline,
@nospecialize,
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove from exports? Or maybe export from Base.Meta? Seems special-purpose enough that it doesn't need to be in the default namespace.

src/jltypes.c Outdated
@@ -1937,10 +1933,11 @@ void jl_init_types(void)
"invokes",
"nargs",
"called",
"nospec",
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no abbreviations. perhaps call this nospecialize?

src/method.c Outdated
jl_printf(JL_STDERR,
"WARNING: @nospecialize annotation applied to a non-argument.\n");
}
else if (sn >= sizeof(m->nospec)*8) {
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces around binary operator

src/method.c Outdated
if (sn >= 0) { // @nospecialize on self is valid but currently ignored
if (sn > (m->nargs - 2)) {
jl_printf(JL_STDERR,
"WARNING: @nospecialize annotation applied to a non-argument.\n");
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this an error

src/gf.c Outdated
@@ -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))) &&
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i <= sizeof(m->nospec) * 8

src/ast.scm Outdated
@@ -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)))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add another parameter one::Bool and check (if one (eq? (length e) 3) (length> e 2))

@@ -3780,6 +3796,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))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

((nospecialize-meta e #f)

@JeffBezanson
Copy link
Sponsor Member Author

Thanks. All comments addressed.

@@ -17,6 +17,42 @@ end
macro _noinline_meta()
Expr(:meta, :noinline)
end

"""
@nospecialize
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether it should be documented as Base.@nospecialize ?

Copy link
Sponsor Member Author

@JeffBezanson JeffBezanson Jul 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, not having it exported was pretty annoying. That's partly just an artifact of Base and tests using this more heavily than most code, but (1) other annotations like @inline are exported, and (2) it's hard to imagine @nospecialize conflicting with anything. So I'm more inclined to just export it.

idx = reinterpret(Int32, f) + 1
t_ifunc[idx] = (minarg, maxarg, tfunc)
t_ifunc_cost[idx] = cost
end
function add_tfunc(#=@nospecialize::Builtin=# f::Function, minarg::Int, maxarg::Int, tfunc::ANY, cost::Int)
function add_tfunc(#=@nospecialize::Builtin=# f::Function, minarg::Int, maxarg::Int, @nospecialize(tfunc), cost::Int)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was the #=@nospecialize::Builtin=# left as a todo for this pr to address?

ccall(:jl_toplevel_eval_in, Any, (Any, Any),
Core, quote
(f::typeof(Typeof))(x) = ($(_expr(:meta,:nospecialize,:x)); isa(x,Type) ? Type{x} : typeof(x))
end)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does this still need the more complex form? Also, why does it need f::typeof(Typeof) – could we just use Typeof(@nospecialize x) = ...

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because Typeof itself is used to lower method definitions not of the form (f::T)(...) = ....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:types and dispatch Types, subtyping and method dispatch kind:deprecation This change introduces or involves a deprecation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants