-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Conversation
@@ -1285,6 +1285,7 @@ export | |||
@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/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) |
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
@@ -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 |
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! |
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.", |
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
@@ -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} |
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.
base/deprecated.jl
Outdated
@@ -1519,6 +1519,8 @@ function replace(s::AbstractString, pat, f, n::Integer) | |||
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?
@@ -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; |
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. |
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).
base/essentials.jl
Outdated
@@ -17,6 +17,38 @@ end | |||
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.
@@ -1285,6 +1285,7 @@ export | |||
@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
@@ -1937,10 +1933,11 @@ void jl_init_types(void) | |||
"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
@@ -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))) && |
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
@@ -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))) |
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
@@ -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)) |
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. |
@@ -17,6 +17,42 @@ end | |||
macro _noinline_meta() | |||
Expr(:meta, :noinline) | |||
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
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) |
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:
ANY
in Base