-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
RFC: replace ANY with @nospecialize annotation
#22666
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
Conversation
| @simd, | ||
| @inline, | ||
| @noinline, | ||
| @nospecialize, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 B.M.x == 4 | ||
|
|
||
| # specialization annotations | ||
| function _nospec_some_args(@nospecialize(x), y, @nospecialize z::Int) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
Is there any functional difference between this and |
|
No. But it will allow additional functionality in the future, such as |
src/julia.h
Outdated
|
|
||
| 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
|
Very nice - I like this a lot! |
| 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.", |
There was a problem hiding this comment.
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
| @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} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
| end | ||
| end | ||
|
|
||
| # ::ANY is deprecated in src/method.c |
There was a problem hiding this comment.
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?
| 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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
43643d2 to
bcbfefb
Compare
|
@nanosoldier |
|
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
b481326 to
fc8d192
Compare
|
FYI there are a couple new ANYs in boot.jl. |
vtjnash
left a comment
There was a problem hiding this 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) |
There was a problem hiding this comment.
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).
| macro _noinline_meta() | ||
| Expr(:meta, :noinline) | ||
| end | ||
| """ |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
| @simd, | ||
| @inline, | ||
| @noinline, | ||
| @nospecialize, |
There was a problem hiding this comment.
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
| "invokes", | ||
| "nargs", | ||
| "called", | ||
| "nospec", |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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
| 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))) && |
There was a problem hiding this comment.
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
| (and (pair? e) (eq? (car e) 'kw))) | ||
|
|
||
| (define (nospecialize-meta? e) | ||
| (and (length> e 2) (eq? (car e) 'meta) (eq? (cadr e) 'nospecialize))) |
There was a problem hiding this comment.
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))
src/julia-syntax.scm
Outdated
| ((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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
((nospecialize-meta e #f)
|
Thanks. All comments addressed. |
| end | ||
|
|
||
| """ | ||
| @nospecialize |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
base/inference.jl
Outdated
| 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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) = ...
There was a problem hiding this comment.
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)(...) = ....
otherwise we can run into `Union{Bool,Int} !== Union{Int,Bool}`
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:
ANYin Base