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

Static evaluation of method_exists (when true) #16422

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion base/docs/helpdb/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6908,7 +6908,7 @@ EnvHash
method_exists(f, Tuple type) -> Bool

Determine whether the given generic function has a method matching the given
[`Tuple`](:obj:`Tuple`) of argument types.
[`Tuple`](:obj:`Tuple`) of argument types. See also `@method_exists`.

```jldoctest
julia> method_exists(length, Tuple{Array})
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1358,6 +1358,7 @@ export
@which,
@edit,
@functionloc,
@method_exists,
@less,
@code_typed,
@code_warntype,
Expand Down
34 changes: 34 additions & 0 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,40 @@ function method_exists(f::ANY, t::ANY)
return ccall(:jl_method_exists, Cint, (Any, Any), typeof(f).name.mt, t) != 0
end

# For static evaluation of method_exists, when it evaluates to true.
# We don't evaluate it at compile-time when false, because someone
# might define the missing method later.
"""
@method_exists(f, tt)

Determine whether the given generic function `f` has a method matching
the given tuple `tt` of argument types. When used in a function, if
the method exists at the time the function is compiled, this statement
simply gets replaced by `true` (and hence has no runtime overhead). If
the function does not exist, then it is equivalent to the function
form, `method_exists`.

```jldoctest
julia> @method_exists(length, Tuple{Array})
true
```
"""
:@method_exists

macro method_exists(f, tt)
exmt = :(typeof($f).name.mt) # get the method table for f
# Form the tuple-type of the function + argument types
if tt.head == :curly && tt.args[1] == :Tuple
extt = :(Tuple{typeof($f), $(tt.args[2:end]...)})
elseif tt.head == :tuple
extt = :(Tuple{typeof($f), $(tt.args...)})
else
error("expected a tuple or tuple-type")
end
# Issue the ccall
esc(:(ccall(:jl_method_exists, Cint, (Any, Any), $exmt, $extt) != 0))
end

function isambiguous(m1::Method, m2::Method)
ti = typeintersect(m1.sig, m2.sig)
ti === Bottom && return false
Expand Down
13 changes: 12 additions & 1 deletion doc/stdlib/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -626,13 +626,24 @@ Generic Functions

.. Docstring generated from Julia source

Determine whether the given generic function has a method matching the given :obj:`Tuple` of argument types.
Determine whether the given generic function has a method matching the given :obj:`Tuple` of argument types. See also ``@method_exists``\ .

.. doctest::

julia> method_exists(length, Tuple{Array})
true

.. function:: @method_exists(f, tt)

.. Docstring generated from Julia source

Determine whether the given generic function ``f`` has a method matching the given tuple ``tt`` of argument types. When used in a function, if the method exists at the time the function is compiled, this statement simple gets replaced by ``true`` (and hence has no runtime overhead). If the function does not exist, then it is equivalent to the function form, ``method_exists``\ .

.. doctest::

julia> @method_exists(length, Tuple{Array})
true

.. function:: applicable(f, args...) -> Bool

.. Docstring generated from Julia source
Expand Down
15 changes: 15 additions & 0 deletions src/ccall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,21 @@ static jl_cgval_t emit_ccall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx)
false, args[2], rt, static_rt, ctx);
}
}
if (fptr == (void(*)(void))&jl_method_exists ||
((f_lib==NULL || (intptr_t)f_lib==2)
&& f_name && !strcmp(f_name, "jl_method_exists"))) {
assert(nargt == 2);
jl_value_t *mt = static_eval(args[4], ctx, false, false);
jl_value_t *usertypes = args[6];
if (mt != NULL && jl_is_mtable(mt) && jl_is_tuple_type(usertypes)) {
int exists = jl_method_exists((jl_methtable_t*)mt, (jl_tupletype_t*)usertypes);
if (exists) {
JL_GC_POP();
return mark_or_box_ccall_result(ConstantInt::get(T_int32, exists),
false, args[2], rt, static_rt, ctx);
}
}
}
if (fptr == (void(*)(void))&jl_function_ptr ||
((f_lib==NULL || (intptr_t)f_lib==2)
&& f_name && !strcmp(f_name, "jl_function_ptr"))) {
Expand Down
18 changes: 12 additions & 6 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1603,13 +1603,19 @@ jl_value_t *jl_static_eval(jl_value_t *ex, void *ctx_, jl_module_t *mod,
jl_value_t *f = jl_static_eval(jl_exprarg(e,0),ctx,mod,linfo,sparams,allow_alloc);
if (f) {
if (jl_array_dim0(e->args) == 3 && f==jl_builtin_getfield) {
m = (jl_module_t*)jl_static_eval(jl_exprarg(e,1),ctx,mod,linfo,sparams,allow_alloc);
jl_value_t* e1 = jl_static_eval(jl_exprarg(e,1),ctx,mod,linfo,sparams,allow_alloc);
s = (jl_sym_t*)jl_static_eval(jl_exprarg(e,2),ctx,mod,linfo,sparams,allow_alloc);
if (m && jl_is_module(m) && s && jl_is_symbol(s)) {
jl_binding_t *b = jl_get_binding(m, s);
if (b && b->constp) {
if (b->deprecated) cg_bdw(b, ctx);
return b->value;
if (s && jl_is_symbol(s)) {
if (e1 && jl_is_module(e1)) {
m = (jl_module_t*)e1;
jl_binding_t *b = jl_get_binding(m, s);
if (b && b->constp) {
if (b->deprecated) cg_bdw(b, ctx);
return b->value;
}
}
else if (e1 && (jl_is_datatype(e1) || jl_is_typename(e1))) {
return jl_get_field(e1, jl_symbol_name(s));
Copy link
Member

Choose a reason for hiding this comment

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

not all of the fields in typename are constant

Copy link
Member Author

@timholy timholy Jun 1, 2016

Choose a reason for hiding this comment

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

This only needs the mt field, is that safe? (It also only needs the name field of a datatype, so if there's any danger there I could restrict it.) EDIT: of course the method table changes as you add new methods, but presumably the table itself is constant?

Copy link
Member

Choose a reason for hiding this comment

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

No, the table itself is not constant

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah. To me that sounds like "we should close this without merging." Or, if the overall functionality is desirable, "find a way to do it that doesn't involve jl_static_eval, so it doesn't imply this is safe generally."

}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,9 @@ STATIC_INLINE jl_function_t *jl_get_function(jl_module_t *m, const char *name)
JL_DLLEXPORT void jl_module_run_initializer(jl_module_t *m);
int jl_is_submodule(jl_module_t *child, jl_module_t *parent);

// methods
JL_DLLEXPORT int jl_method_exists(jl_methtable_t *mt, jl_tupletype_t *types);

// eq hash tables
JL_DLLEXPORT jl_array_t *jl_eqtable_put(jl_array_t *h, void *key, void *val);
JL_DLLEXPORT jl_value_t *jl_eqtable_get(jl_array_t *h, void *key,
Expand Down
41 changes: 41 additions & 0 deletions test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -439,3 +439,44 @@ fLargeTable() = 4
# issue #15280
function f15280(x) end
@test functionloc(f15280)[2] > 0

# Static evaluation of method_exists (#16422)
meth16422(x::Int, y::Int) = 0
function static_meth_exists_tt()
# As a tuple-type
if @method_exists(meth16422, Tuple{Int,Int})
return true
end
false
end
@test static_meth_exists_tt()
io = IOBuffer()
code_llvm(io, static_meth_exists_tt, ())
str = takebuf_string(io)
@test contains(str, "top:\n ret i1 true\n}")

function static_meth_exists_t()
# As a tuple
if @method_exists(meth16422, (Int,Int))
return true
end
false
end
@test static_meth_exists_t()
io = IOBuffer()
code_llvm(io, static_meth_exists_t, ())
str = takebuf_string(io)
@test contains(str, "top:\n ret i1 true\n}")

function static_meth_not_exists()
if @method_exists(meth16422, Tuple{Int,String})
return true
end
false
end
@test !static_meth_not_exists()
code_llvm(io, static_meth_not_exists, ())
str = takebuf_string(io)
@test !contains(str, "ret i1 true") && !contains(str, "ret i1 false")
meth16422(x::Int, y::String) = 1
@test static_meth_not_exists() # define it later, should be true